فهرست منبع

feat: 阶段评测逻辑

TIANYONG 1 سال پیش
والد
کامیت
938c2b03ee

+ 55 - 1
src/helpers/formateMusic.ts

@@ -591,6 +591,9 @@ export const formatXML = (xml: 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'))
+	// 处理重复小节信息
+	parseXmlToRepeat(repeats)
 	// const words: any = xmlParse.getElementsByTagName("words");
 	// for (const word of words) {
 	// 	if (word && word.textContent?.trim() === "筒音作5") {
@@ -1034,11 +1037,62 @@ export const getNoteByMeasuresSlursStart = (note: any) => {
 		tieNote = note.noteElement.tie.StartNote;
 	}
 	if (activeNote && tieNote && tieNote !== activeNote.noteElement) {
+		const arr: any = []
 		for (const note of state.times) {
 			if (tieNote === note.noteElement) {
-				return note;
+				arr.push(note)
 			}
 		}
+		if (arr.length) {
+			return arr.find((n: any) => n.i === (note.i - 1))
+		}
 	}
 	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) => {
+	if (state.repeatInfo.length) {
+		for (let i = 0; i < state.repeatInfo.length; i++) {
+			const element = state.repeatInfo[i];
+			const { start, end } = state.repeatInfo[i];
+			if (startNum <= start && endNum >= end) {
+				return true
+				break;
+			}
+		}
+		return false
+	} else {
+		return false
+	}
+}

+ 3 - 28
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 { verifyMembershipServices } from "../vip-verify";
 import { api_lessonTrainingSubmitTraining } from "../../api";
-import state, { IDifficulty, handleSetSpeed, hanldeDirectSelection } from "/src/state";
+import state, { IDifficulty, handleSetSpeed, hanldeDirectSelection, setSection } from "/src/state";
 import { getQuery } from "/src/utils/queryString";
 import { evaluatingData } from "/src/view/evaluating";
 
@@ -49,35 +49,10 @@ export default defineComponent({
 				evaluatingWorkData.evaluateSpeed = trainingContent.evaluateSpeed;
 				evaluatingWorkData.start = Number(trainingContent.practiceChapterBegin);
 				evaluatingWorkData.end = Number(trainingContent.practiceChapterEnd);
-				setSection();
+				// 设置小节
+				setSection(evaluatingWorkData.start, evaluatingWorkData.end, evaluatingWorkData.evaluateSpeed);
 			}
 		};
-		/**设置小节 */
-		const setSection = () => {
-			const startNotes = state.times.filter(
-			  (n: any) => n.noteElement.sourceMeasure.MeasureNumberXML == evaluatingWorkData.start
-			)
-			const endNotes = state.times.filter(
-			  (n: any) => n.noteElement.sourceMeasure.MeasureNumberXML == evaluatingWorkData.end
-			)
-			const startNote = startNotes[0]
-			const endNote = endNotes[endNotes.length - 1]
-			//   console.log('🚀 ~ activeNote', startNote, endNote, questionExtendsInfo.value.end)
-			if (startNote && endNote) {
-			  state.isSelectMeasureMode = true;
-			  // 设置小节
-			  hanldeDirectSelection([startNote, endNote]);
-
-			//   // 设置小节
-			//   state.sectionStatus = true
-			//   state.section = [startNote, endNote]
-
-			  //设置速度
-			  if (evaluatingWorkData.evaluateSpeed) {
-				handleSetSpeed(evaluatingWorkData.evaluateSpeed);
-			  }
-			}
-		  }		
 		/** 添加记录 */
 		const addEvaluatingWorkRecored = async (data: any) => {
 			try {

+ 4 - 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 { verifyMembershipServices } from "../vip-verify";
 import { api_lessonTrainingSubmitTraining, api_lessonTrainingTrainingStudentDetail } from "../../api";
-import state, { handleSetSpeed, hanldeDirectSelection } from "/src/state";
+import state, { handleSetSpeed, hanldeDirectSelection, setSection } from "/src/state";
 
 export default defineComponent({
 	name: "HomeWork",
@@ -52,32 +52,12 @@ export default defineComponent({
 				training.start = Number(trainingContent.practiceChapterBegin);
 				training.end = Number(trainingContent.practiceChapterEnd);
 				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.isSelectMeasureMode = true;
-			  // 设置小节
-			  hanldeDirectSelection([startNote, endNote]);
-			  //设置速度
-			  if (training.trainingSpeed) {
-				handleSetSpeed(training.trainingSpeed);
-			  }
-			}
-		  }
+
 		const getWorkDetail = async () => {
 			const res = await api_lessonTrainingTrainingStudentDetail(props.workeData.id);
 			if (res?.code === 200) {

+ 40 - 10
src/page-instrument/evaluat-model/index.tsx

@@ -30,6 +30,7 @@ type TCriteria = "frequency" | "amplitude" | "decibels";
  * 阶段评测时,判断是否从第一小节开始,并且曲子本身含有节拍器,需要传节拍器时长,否则传0
  */
 let actualBeatLength = 0
+let calculateInfo: any = {}
 
 export default defineComponent({
   name: "evaluat-model",
@@ -107,25 +108,46 @@ export default defineComponent({
 
     /** 生成评测曲谱数据 */
     const formatTimes = () => {
+      let starTime = 0
       let ListenMode = false;
       let dontEvaluatingMode = false;
       let skip = false;
       const datas = [];
       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
 				)
-				const endIndex = state.times.findIndex(
+				let endIndex = state.times.findIndex(
 				  (n: any) => n.noteId == state.section[1].noteId
 				)
-				actualBeatLength = startIndex == 0 && !state.needTick ? actualBeatLength : 0
+        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];
@@ -134,8 +156,8 @@ export default defineComponent({
         // const rate = state.speed / state.originSpeed;
         const rate = 1;
         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 noteRate = isStaccato ? 0.5 : 1;
         if (note.formatLyricsEntries.contains("Play") || note.formatLyricsEntries.contains("Play...")) {
@@ -161,6 +183,10 @@ export default defineComponent({
         // console.log(note.measureOpenIndex, item.measureOpenIndex, note);
         // console.log("skip", skip)
         // console.log(end,start,rate,noteRate, '评测')
+        if (note.measureOpenIndex != recordMeasure) {
+					measureIndex++
+					recordMeasure = note.measureOpenIndex
+				}
         const data = {
           timeStamp: (start * 1000) / rate,
           duration: ((end * 1000) / rate - (start * 1000) / rate) * noteRate,
@@ -168,23 +194,27 @@ export default defineComponent({
           nextFrequency: item.nextFrequency,
           prevFrequency: item.prevFrequency,
           // 重复的情况index会自然累加,render的index是谱面渲染的index
-          measureIndex: note.measureOpenIndex,
+          measureIndex: measureIndex,
           measureRenderIndex: item.measureListIndex,
           dontEvaluating: ListenMode || dontEvaluatingMode || item.skipMode,
-          musicalNotesIndex: item.i,
+          musicalNotesIndex: index,
           denominator: note.noteElement?.Length.denominator,
           isOrnament: !!note?.voiceEntry?.ornamentContainer,
         };
         datas.push(data);
       }
-      return datas;
+			return {
+				datas,
+				firstNoteTime
+			}
     };
     /** 连接websocket */
     const handleConnect = async () => {
       const behaviorId = localStorage.getItem("behaviorId") || undefined;
       const rate = state.speed / state.originSpeed;
+      calculateInfo = formatTimes()
       const content = {
-        musicXmlInfos: formatTimes(),
+        musicXmlInfos: calculateInfo.datas,
         subjectId: state.subjectId,
         detailId: state.detailId,
         examSongId: state.examSongId,
@@ -231,7 +261,7 @@ export default defineComponent({
         handleStartEvaluat();
       } else if (type === "tryagain") {
         // 再来一次
-        handleStartBegin();
+        startBtnHandle()
       }
       evaluatingData.resulstMode = false;
     };
@@ -270,7 +300,7 @@ export default defineComponent({
 
     const startBtnHandle = () => {
       handleConnect();
-      handleStartBegin();
+      handleStartBegin(calculateInfo.firstNoteTime);
     }
     onMounted(() => {
       evaluatingData.isDisabledPlayMusic = true;

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

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

+ 54 - 3
src/state.ts

@@ -10,6 +10,7 @@ import { audioListStart, getAudioCurrentTime, getAudioDuration, setAudioCurrentT
 import { toggleFollow } from "./view/follow-practice";
 import { browser, setStorageSpeed } from "./utils";
 import { api_createMusicPlayer } from "./helpers/communication";
+import { verifyCanRepeat } from "./helpers/formateMusic";
 
 /** 入门 | 进阶 | 大师 */
 export type IDifficulty = "BEGINNER" | "ADVANCED" | "PERFORMER";
@@ -180,7 +181,11 @@ const state = reactive({
   /** 是否隐藏评测报告弹窗,保存演奏按钮,默认不隐藏 */
   isHideEvaluatReportSaveBtn: false,
   /** 是否是合奏 */
-  isConcert: false
+  isConcert: false,
+  /** 用户选择的结束小节数 */
+  userChooseEndIndex: 0,
+  /** 重播小节集合信息 */
+  repeatInfo: [],  
 });
 const browserInfo = browser();
 let offset_duration = 0;
@@ -254,7 +259,7 @@ const handlePlaying = () => {
   const duration = getAudioDuration();
   state.playProgress = (currentTime / duration) * 100;
   let item = getNote(currentTime);
-  // console.log(11111,currentTime,duration,state.playSource)
+  // console.log(11111,currentTime,duration,state.playSource, item.i)
   if (item) {
     // 选段状态下
     if (state.sectionStatus && state.section.length === 2) {
@@ -374,6 +379,7 @@ const setCursorPosition = (note: any, cursor: any) => {
       if (!bbox) return;
       const baseW = state.platform === IPlatform.PC ? 29 : 18;
       const width = (bbox.width - baseW) / 3;
+      // console.log(555555,bbox.left,width)
       cursor.cursorElement.style.left = bbox.left + "px";
       cursor.cursorElement.style.transform = `translateX(${width}px)`;
     });
@@ -381,6 +387,7 @@ const setCursorPosition = (note: any, cursor: any) => {
 };
 /** 跳转到下一个音符 */
 export const gotoNext = (note: any) => {
+  // console.log(33333333333,state.activeNoteIndex,note.i)
   const num = note.i;
 
   if (state.activeNoteIndex === note.i) {
@@ -391,6 +398,7 @@ export const gotoNext = (note: any) => {
     }
     return;
   }
+  
   const osmd = state.osmd;
   let prev = state.activeNoteIndex;
   state.activeNoteIndex = num;
@@ -525,14 +533,57 @@ 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 isCanRepeat = verifyCanRepeat(start, end)
+  // 如果符合重播规则,但是lastEndNotes长度为1,则需要向前找,直到找到lastEndNotes长度为2
+  let currentEndNum: number = end
+  const lastEndIndex = state.repeatInfo.last()?.end
+  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 ? 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[]) => {
   if (!Array.isArray(list) || list.length !== 2) return;
   state.sectionStatus = true;
   setTimeout(() => {
     state.section = formateSelectMearure(list);
+    console.log(333333333,state.section)
   }, 500);
-  
 };
 let offsetTop = 0;
 /**

+ 3 - 1
src/view/audio-list/index.tsx

@@ -56,6 +56,7 @@ export const getAudioCurrentTime = () => {
 		const c = getMidiCurrentTime();
 		return c;
 	}
+	// console.log('返回的时间',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;
 	
@@ -147,11 +148,12 @@ export default defineComponent({
 		};
 		// 监听评测曲谱音频播放进度,返回
 		const progress = (res: any) => {
-			// console.log(res,'播放进度111')
 			const currentTime = res?.currentTime || res?.content?.currentTime;
 			const total = res?.totalDuration || res?.content?.totalDuration;
 			const time = currentTime / 1000;
 			audioData.progress = time;
+			audioData.songEle && (audioData.songEle.currentTime = time);
+			audioData.backgroundEle && (audioData.backgroundEle.currentTime = time);
 			audioData.duration = total / 1000;
 			if (
 				res?.content?.totalDuration > 1000 &&

+ 3 - 2
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.evaluatings = {};
 	evaluatingData.resultData = {};
@@ -271,7 +271,8 @@ export const handleStartBegin = async () => {
 	}
 	//开始录音
 	await api_startRecording({
-		accompanimentState: state.setting.enableAccompaniment ? 1 : 0
+		accompanimentState: state.setting.enableAccompaniment ? 1 : 0,
+		firstNoteTime: preTimes || 0,
 	});
 
 	// 如果开启了摄像头, 开启录制视频