瀏覽代碼

fix:跟练

liushengqiang 2 年之前
父節點
當前提交
77eb6006b3

+ 23 - 7
src/page-instrument/follow-model/index.module.less

@@ -1,10 +1,4 @@
-:global{
-    .green{
-        .vf-notehead > path {
-            fill: green;
-        }
-    }
-}
+
 .startBtn {
   position: fixed;
   left: 50%;
@@ -34,3 +28,25 @@
     height: 100%;
   }
 }
+
+.noteState {
+  position: fixed;
+  bottom: 0;
+  right: 10PX;
+  width: 100Px;
+  height: 30Px;
+  background-color: rgba(0, 0, 0, 0.5);
+  z-index: 1000;
+  border-radius: 4Px;
+  display: flex;
+  align-items: center;
+  justify-content: space-evenly;
+  color: #fff;
+
+  .dot {
+      width: 13Px;
+      height: 10Px;
+      border-radius: 50%;
+      transform: rotate(-20deg);
+  }
+}

+ 8 - 2
src/page-instrument/follow-model/index.tsx

@@ -1,7 +1,7 @@
 import { Transition, defineComponent, onBeforeUnmount, onMounted, ref } from "vue";
 import styles from "./index.module.less";
 import icons from "./icons.json";
-import { handleFollowEnd, handleFollowStart } from "/src/view/follow-practice";
+import { followData, handleFollowEnd, handleFollowStart } from "/src/view/follow-practice";
 
 export default defineComponent({
 	name: "follow-model",
@@ -37,13 +37,19 @@ export default defineComponent({
 								src={icons.end}
 								onClick={() => {
 									startBtn.value = true;
-                                    endBtn.value = false;
+									endBtn.value = false;
 									handleFollowEnd();
 								}}
 							/>
 						</div>
 					)}
 				</Transition>
+				<div style={{ display: followData.start ? "" : "none" }} class={styles.noteState}>
+					<span style={{ background: "#ffca67" }} class={styles.dot}></span>
+					<span>低</span>
+					<span style={{ background: "rgb(255, 0, 0)" }} class={styles.dot}></span>
+					<span>高</span>
+				</div>
 			</>
 		);
 	},

+ 57 - 41
src/view/follow-practice/index.tsx

@@ -44,35 +44,38 @@ const onClear = () => {
 	state.times.forEach((item: any) => {
 		const note: HTMLElement = document.querySelector(`div[data-vf=vf${item.id}]`)!;
 		if (note) {
-			note.classList.remove("error");
-			note.classList.remove("success");
+			note.classList.remove("follow-up", "follow-down", "follow-error", "follow-success");
+		}
+		const _note: HTMLElement = document.getElementById(`vf-${item.id}`)!;
+		if (_note) {
+			_note.classList.remove("follow-up", "follow-down");
 		}
 	});
 };
 
 /** 开始跟练 */
 export const handleFollowStart = () => {
-	if (!storeData.isApp) {
-		Snackbar({
-			content: "请在APP端使用",
-			type: "warning",
-		});
-		return;
-	}
+	// if (!storeData.isApp) {
+	// 	Snackbar({
+	// 		content: "请在APP端使用",
+	// 		type: "warning",
+	// 	});
+	// 	return;
+	// }
 	onClear();
 	followData.start = true;
 	followData.index = 0;
 	followData.list = [];
 	resetPlaybackToStart();
 	openToggleRecord(true);
-	setStep();
+	getNoteIndex()
 };
 /** 结束跟练 */
 export const handleFollowEnd = () => {
 	followData.start = false;
 	openToggleRecord(false);
 	followData.index = 0;
-	console.log('结束')
+	console.log("结束");
 };
 
 // 下一个
@@ -92,59 +95,72 @@ const getNoteIndex = (): any => {
 	// state.fixedKey = item.realKey;
 	return {
 		id: item.id,
-		min: item.frequency - item.prevFrequency * 0.1,
-		max: item.frequency + item.nextFrequency * 0.1,
+		min: item.frequency - (item.frequency - item.prevFrequency) * 0.5,
+		max: item.frequency + (item.nextFrequency - item.frequency) * 0.5,
+		duration: item.duration,
+		baseFrequency: item.frequency,
 	};
 };
 let checking = false;
 /** 录音回调 */
 const onFollowTime = (evt?: IPostMessage) => {
 	const frequency: number = evt?.content?.frequency;
-	audioFrequency.value = frequency;
-	followData.list.push(frequency);
-};
-
-/** 在渲染前后计算光标应该走到的音符 */
-const setStep = () => {
-	if (!followData.start) {
-		return;
+	// 没有开始, 不处理
+	if (!followData.start) return
+	// console.log('频率', frequency)
+	if (frequency > 0) {
+	  audioFrequency.value = frequency
+	  // data.list.push(frequency)
+	  checked()
 	}
-	checked();
-	setTimeout(() => {
-		setStep();
-	}, 50)
 };
 
+let startTime = 0;
 const checked = () => {
 	if (checking) return;
 	checking = true;
 	const item = getNoteIndex();
-	for (let i = 0; i < followData.list.length; i++) {
-		const frequency = followData.list[i];
-		if (frequency > item.min && frequency < item.max) {
-			// console.log(item.min, frequency, item.max);
-			next();
-			followData.index += 1;
-			followData.list = followData.list.slice(i + 1);
-			setColor(item, true);
-			checking = false;
-			return;
+	// 降噪处理, 频率低于 当前音的50% 时, 不处理
+	if (audioFrequency.value < item.baseFrequency * 0.5) {
+		checking = false;
+		return;
+	}
+	if (audioFrequency.value >= item.min && audioFrequency.value <= item.max) {
+		if (startTime === 0) {
+			startTime = Date.now();
+		} else {
+			const playTime = (Date.now() - startTime) / 1000;
+			// console.log('时长', playTime, item.duration / 2)
+			if (playTime >= item.duration * 0.6) {
+				startTime = 0;
+				followData.index = followData.index + 1;
+				setColor(item, "", true);
+				next();
+				checking = false;
+				return;
+			}
 		}
+		console.log("频率对了", item.min, audioFrequency.value, item.max, item.duration);
 	}
-	setColor(item);
+	console.log("频率不对", item.min, audioFrequency.value, item.max, item.baseFrequency);
+	setColor(item, audioFrequency.value > item.baseFrequency ? "follow-up" : "follow-down");
 	checking = false;
 };
-const setColor = (item: any, isRight = false) => {
+const setColor = (item: any, state: "follow-up" | "follow-down" | "", isRight = false) => {
 	const note: HTMLElement = document.querySelector(`div[data-vf=vf${item.id}]`)!;
 	if (note) {
+		note.classList.remove("follow-up", "follow-down", "follow-error", "follow-success");
 		if (isRight) {
-			note.classList.remove("error");
-			note.classList.add("success");
+			note.classList.add("follow-success");
 		} else {
-			note.classList.remove("success");
-			note.classList.add("error");
+			note.classList.add("follow-error", state);
 		}
 	}
+	const _note: HTMLElement = document.getElementById(`vf-${item.id}`)!;
+	if (_note) {
+		_note.classList.remove("follow-up", "follow-down");
+		state && _note.classList.add(state);
+	}
 };
 
 export default defineComponent({

+ 43 - 16
src/view/selection/index.module.less

@@ -165,32 +165,59 @@
     font-size: 14px;
 }
 
-.noteFollow{
+.noteFollow {
     pointer-events: none;
     text-align: center;
     display: none;
-    :global{
+
+    :global {
+
         .van-icon-success,
-        .van-icon-cross{
+        .van-icon-cross {
             display: none;
         }
     }
-    &:global(.success){
-        color: #07c160;
+}
+
+:global {
+    .follow-error {
         display: block;
-        :global{
-            .van-icon-success{
-                display: initial;
-            }
+
+        .van-icon-cross {
+            display: block;
         }
+
     }
-    &:global(.error){
-        color: #ee0a24;
+
+    .follow-down {
+        .van-icon-cross {
+            color: #ffca67 !important;
+        }
+
+        .vf-note path {
+            fill: #ffca67 !important;
+            stroke: #ffca67 !important;
+        }
+    }
+
+    .follow-up {
+        .van-icon-cross {
+            color: rgb(255, 0, 0) !important;
+        }
+
+        .vf-note path {
+            fill: rgb(255, 0, 0) !important;
+            stroke: rgb(255, 0, 0) !important;
+        }
+    }
+
+    .follow-success {
         display: block;
-        :global{
-            .van-icon-cross{
-                display: initial;
-            }
+
+        .van-icon-success {
+            display: block;
+            color: #07c160;
         }
     }
-}
+}
+

+ 45 - 12
src/view/selection/index.tsx

@@ -16,7 +16,10 @@ const selectData = reactive({
 
 /** 计算点击层数据 */
 const calcNoteData = () => {
-	const musicContainer = document.getElementById("musicAndSelection")?.getBoundingClientRect() || { x: 0, y: 0 };
+	const musicContainer = document.getElementById("musicAndSelection")?.getBoundingClientRect() || {
+		x: 0,
+		y: 0,
+	};
 	const parentLeft = musicContainer.x || 0;
 	const parentTop = musicContainer.y || 0;
 	const notes = state.times;
@@ -35,7 +38,10 @@ const calcNoteData = () => {
 			let staveBbox: any = {};
 			if (item.stave?.attrs?.id) {
 				const staveEle = document.querySelector(`#${item.stave.attrs.id}`);
-				staveBbox = staveEle?.parentElement?.parentElement?.getBoundingClientRect?.() || { x: 0, width: 0 };
+				staveBbox = staveEle?.parentElement?.parentElement?.getBoundingClientRect?.() || {
+					x: 0,
+					width: 0,
+				};
 				// console.log("🚀 ~ staveBbox:", staveBbox)
 			}
 			if (item.svgElement) {
@@ -96,7 +102,9 @@ const calcNoteData = () => {
 				MeasureNumberXMLList.push(item.MeasureNumberXML);
 			} else {
 				if (item.multipleRestMeasures) {
-					const preItem = selectData.staves.find((n: any) => n.MeasureNumberXML === item.MeasureNumberXML - 1);
+					const preItem = selectData.staves.find(
+						(n: any) => n.MeasureNumberXML === item.MeasureNumberXML - 1
+					);
 					if (preItem?.staveBox) {
 						noteItem.staveBox = {
 							left: preItem.staveBox.left,
@@ -145,8 +153,14 @@ export default defineComponent({
 						if (state.sectionFirst && item.MeasureNumberXML === state.sectionFirst.MeasureNumberXML) {
 							return styles.prepareStaveBox;
 						}
-						if (item.MeasureNumberXML >= state.section[0].MeasureNumberXML && item.MeasureNumberXML <= state.section[1].MeasureNumberXML) {
-							if (item.MeasureNumberXML == state.section[0].MeasureNumberXML && item.MeasureNumberXML == state.section[1].MeasureNumberXML) {
+						if (
+							item.MeasureNumberXML >= state.section[0].MeasureNumberXML &&
+							item.MeasureNumberXML <= state.section[1].MeasureNumberXML
+						) {
+							if (
+								item.MeasureNumberXML == state.section[0].MeasureNumberXML &&
+								item.MeasureNumberXML == state.section[1].MeasureNumberXML
+							) {
 								return styles.centerStaveBox;
 							}
 							if (item.MeasureNumberXML == state.section[0].MeasureNumberXML) {
@@ -169,7 +183,11 @@ export default defineComponent({
 			calcNoteData();
 		});
 		return () => (
-			<div id="selectionBox" class={styles.selectionContainer} onClick={(e: Event) => e.stopPropagation()}>
+			<div
+				id="selectionBox"
+				class={styles.selectionContainer}
+				onClick={(e: Event) => e.stopPropagation()}
+			>
 				{selectData.staves.map((item: any) => {
 					const scoreItem = evaluatingData.evaluatings[item.measureListIndex];
 					// 高级模式下,显示节拍线
@@ -188,14 +206,22 @@ export default defineComponent({
 						<>
 							{item.staveBox && (
 								<div
-									class={[styles.position, showClass.value(item), scoreItem ? `scoreItemLeve${scoreItem.leve}` : ""]}
+									class={[
+										styles.position,
+										showClass.value(item),
+										scoreItem ? `scoreItemLeve${scoreItem.leve}` : "",
+									]}
 									style={item.staveBox}
 									onClick={() => handleSelection(item)}
 								>
-									{lineShow && <div class={styles.line} style={{ left: metronomeData.activeMetro.left }}></div>}
-									{!state.isReport && !!item.multipleRestMeasures && state.activeMeasureIndex == item.MeasureNumberXML && (
-										<div class={styles.dotWrap}>{item.multipleRestMeasures}</div>
+									{lineShow && (
+										<div class={styles.line} style={{ left: metronomeData.activeMetro.left }}></div>
 									)}
+									{!state.isReport &&
+										!!item.multipleRestMeasures &&
+										state.activeMeasureIndex == item.MeasureNumberXML && (
+											<div class={styles.dotWrap}>{item.multipleRestMeasures}</div>
+										)}
 									<Transition
 										name="centerTop"
 										onAfterEnter={() => {
@@ -203,7 +229,10 @@ export default defineComponent({
 										}}
 									>
 										{scoreItem?.show && (
-											<div class={styles.scoreItem} style={{ color: leveByScoreMeasureIcons[scoreItem.leve]?.color || "" }}>
+											<div
+												class={styles.scoreItem}
+												style={{ color: leveByScoreMeasureIcons[scoreItem.leve]?.color || "" }}
+											>
 												<img src={leveByScoreMeasureIcons[scoreItem.leve]?.icon} />
 												<span>{scoreItem.score}</span>
 											</div>
@@ -216,7 +245,11 @@ export default defineComponent({
 				})}
 				{selectData.notes.map((item: any) => {
 					return (
-						<div class={[styles.position, disableClickNote.value && styles.disable, styles.note]} style={item.bbox} onClick={() => skipNotePlay(item.index)}>
+						<div
+							class={[styles.position, disableClickNote.value && styles.disable, styles.note]}
+							style={item.bbox}
+							onClick={() => skipNotePlay(item.index)}
+						>
 							<div class={styles.noteFollow} data-vf={"vf" + item.id}>
 								<Icon name="success" />
 								<Icon name="cross" />