فهرست منبع

系统节拍器改版

黄琪勇 1 سال پیش
والد
کامیت
26e4a5242e

+ 1 - 1
src/page-instrument/evaluat-model/index.tsx

@@ -148,7 +148,7 @@ export default defineComponent({
 					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
+        actualBeatLength = startIndex == 0 && state.isOpenMetronome ? actualBeatLength : 0
 				selectTimes = state.times.filter((n: any, index: number) => {
 				  return index >= startIndex && index <= endIndex
 				})

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

@@ -428,6 +428,8 @@ export default defineComponent({
             > 
               {/* 旋律线关闭时候的 标题和作者 */}
               <AuthorName></AuthorName>
+              {/* 节拍器 */}
+              {!detailData.isLoading && !detailData.skeletonLoading && <Tick />}
             </MusicScore>
           }
 
@@ -448,10 +450,6 @@ export default defineComponent({
             </div>
           )}
         </div>
-        {/* 节拍器,跟练需要播放系统节拍器,所以不需要判断needTick状态 */}
-        {/* {state.needTick && <Tick />} */}
-        <Tick />
-
         {/* 曲目渲染完成,再去下载mp3资源 */}
         {!detailData.isLoading && !detailData.skeletonLoading && <AudioList />}
 

+ 34 - 15
src/state.ts

@@ -5,7 +5,7 @@ import { metronomeData } from "./helpers/metronome";
 import { GradualNote, GradualTimes, GradualVersion } from "./type";
 import { handleEndEvaluat, handleStartEvaluat } from "./view/evaluating";
 import { IFingering, mappingVoicePart, subjectFingering, matchVoicePart } from "/src/view/fingering/fingering-config";
-import { handleStartTick } from "./view/tick";
+import { handleStartTick, closeTick } from "./view/tick";
 import { audioListStart, getAudioCurrentTime, getAudioDuration, setAudioCurrentTime, setAudioPlaybackRate, audioData } from "./view/audio-list";
 import { toggleFollow } from "./view/follow-practice";
 import { browser, setStorageSpeed, setGlobalData } from "./utils";
@@ -288,7 +288,9 @@ const state = reactive({
   /** 扩展样式字段 */
   extStyleConfigJson: {} as any,
   /** 是否开启节拍器(mp3节拍器) */
-  isOpenMetronome: false,
+  isOpenMetronome: false,  
+  /** 演唱模式是否开启节拍器(mp3节拍器) */
+  isSingOpenMetronome: false,
   /** 是否显示指法 */
   isShowFingering: false,
   /** 原音 */
@@ -329,8 +331,10 @@ const state = reactive({
   track: "",
   /** 当前显示声部索引 */
   partIndex: 0,
-  /** 是否需要节拍器 */
-  needTick: false,
+  /** 演奏是否需要节拍器 */
+  needTick: false, 
+  /** 演唱模式是否需要节拍器 */
+  needSingTick: false,
   /** 曲谱实例 */
   osmd: null as unknown as OpenSheetMusicDisplay,
   /**是否是特殊乐谱类型, 主要针对管乐迷  */
@@ -704,14 +708,23 @@ export const togglePlay = async (playState?: "play" | "paused", sourceType?: str
   if (state.playState === "play" && state.sectionStatus && state.section.length == 2 && state.playProgress === 0) {
     resetPlaybackToStart();
   }
-  // 设置为开始播放时, 如果需要节拍,先播放节拍器
-  if (state.playState === "play" && state.needTick) {
-    const tickend = await handleStartTick();
-    // console.log("🚀 ~ tickend:", tickend)
-    // 节拍器返回false, 取消播放
-    if (!tickend) {
-      state.playState = "paused";
-      return false;
+   // 当在节拍器播放期间暂停的话 就暂停节拍器
+  if(state.playState === "paused") {
+    closeTick()
+  }
+  // 设置为开始播放时, 如果需要节拍,先播放节拍器   只有在当前播放时间不为0的时候开启节拍器
+  if (state.playState === "play" && getAudioCurrentTime()===0 && (state.playType==="play"&&state.needTick)||(state.playType==="sing"&&state.needSingTick)) {
+    // 如果是系统节拍器 等系统节拍器播完了再播,如果是mp3节拍器 直接播
+    if((state.playType==="play" && !state.isOpenMetronome)||(state.playType==="sing" && !state.isSingOpenMetronome)){
+      const tickend = await handleStartTick();
+      // console.log("🚀 ~ tickend:", tickend)
+      // 节拍器返回false, 取消播放
+      if (!tickend) {
+        state.playState = "paused";
+        return false;
+      }
+    }else{
+      handleStartTick()
     }
   }
   // 如果选段没有结束, 直接开始播放,清空选段状态
@@ -1203,7 +1216,9 @@ function xmlToTracks(xmlString: string) {
 // 设置音源
 function initMusicSource (data: any, track?:string) {
   const { instrumentId } = storeData.user
-  const { musicSheetType, isAllSubject, musicSheetSoundList, musicSheetAccompanimentList } = data
+  let { musicSheetType, isAllSubject, musicSheetSoundList, musicSheetAccompanimentList } = data
+  musicSheetSoundList || (musicSheetSoundList = [])
+  musicSheetAccompanimentList || (musicSheetAccompanimentList = [])
   let musicObj,accompanyObj,fanSongObj,banSongObj
   // 独奏
   if(musicSheetType === "SINGLE") {
@@ -1279,10 +1294,14 @@ const setState = (data: any, index: number) => {
   state.gradualTimes = state.extConfigJson.gradualTimes;
   state.repeatedBeats = state.extConfigJson.repeatedBeats || 0;
   state.isEvxml = state.extConfigJson.isEvxml == 1 ? true : false;
-  // 曲子包含节拍器,就不开启节拍器
-  state.needTick = data.isUseSystemBeat && data.isPlayBeat ? true : false;
+  // 是否开启节拍器
+  state.needTick = !!data.isPlayBeat
+  state.needSingTick = !!data.isPlaySingBeat
   // state.isOpenMetronome = data.isUseSystemBeat ? false : true;
+  // 演奏模式是否播mp3节拍器
   state.isOpenMetronome = data.isPlayBeat && !data.isUseSystemBeat ? true : false
+  // 演唱模式是否播mp3节拍器
+  state.isSingOpenMetronome = data.isPlaySingBeat && !data.isUseSingSystemBeat ? true : false
   state.isShowFingering = data.isShowFingering ? true : false;
   // 设置曲谱的播放模式, APP播放(midi音频是app播放) | h5播放
   state.isAppPlay = data.playMode === 'MIDI';

+ 16 - 13
src/view/evaluating/index.tsx

@@ -45,7 +45,7 @@ import { IPostMessage } from "/src/utils/native-message";
 import { usePageVisibility } from "@vant/use";
 import { browser } from "/src/utils";
 import { getAudioCurrentTime, toggleMutePlayAudio, audioListStart, audioData } from "../audio-list";
-import { handleStartTick, tickData } from "../tick";
+import { handleStartTick, closeTick } from "../tick";
 import AbnormalPop from "../abnormal-pop";
 import { storeData } from "../../store";
 import icon_bg from '../abnormal-pop/icon_bg.svg'
@@ -365,14 +365,19 @@ export const handleStartBegin = async (preTimes?: number) => {
 	if (evaluatingData.isDisabledPlayMusic) {
 		state.playState = state.playState === "paused" ? "play" : "paused";
 		// 设置为开始播放时, 如果需要节拍,先播放节拍器
-		if (state.playState === "play" && state.needTick) {
-			const tickend = await handleStartTick();
-			console.log("🚀 ~ tickend:", tickend)
-			// 节拍器返回false, 取消播放
-			if (!tickend) {
-				state.playState = "paused";
-				evaluatingData.startBegin = false;
-				return;
+		if (state.playState === "play" && (state.playType==="play"&&state.needTick)||(state.playType==="sing"&&state.needSingTick)) {
+			// 如果是系统节拍器 等系统节拍器播完了再播,如果是mp3节拍器 直接播
+			if((state.playType==="play" && !state.isOpenMetronome)||(state.playType==="sing" && !state.isSingOpenMetronome)){
+				const tickend = await handleStartTick();
+				console.log("🚀 ~ tickend:", tickend)
+				// 节拍器返回false, 取消播放
+				if (!tickend) {
+					state.playState = "paused";
+					evaluatingData.startBegin = false;
+					return;
+				}
+			}else{
+				handleStartTick()
 			}
 		}
 		onPlay();
@@ -614,10 +619,8 @@ const handleAccompanyError = (res?: IPostMessage) => {
 				if (state.modeType === "evaluating" && evaluatingData.startBegin) {
 					handleCancelEvaluat('cancel');
 				}
-				if (tickData.show) {
-					tickData.tickEnd = true
-					tickData.show = false
-				}
+				// 关闭节拍器
+				closeTick()
 				evaluatingData.socketErrorStatus = 0
 				evaluatingData.socketErrorPop = type === "socketError" ? true : false
 				evaluatingData.isErrorState = true

+ 17 - 20
src/view/tick/index.module.less

@@ -1,26 +1,23 @@
-.popup{
-    background: transparent;
-    width: 100%;
-    height: 100%;
-}
 .dots{
     display: flex;
     justify-content: center;
     align-items: center;
-    width: 100%;
-    height: 100%;
-}
-.dot{
-    width: 30px;
-    height: 30px;
-    margin: 0 20px;
-    background-color: #C0C0C0;
-    border-radius: 50%;
-    
-    &.active{
-        background-color: #fff;
-    }
-    &.one{
-        background-color: var(--van-primary-color);
+    position: absolute;
+    transform: translateY(-100%);
+    .dot{
+        width: 13Px;
+        height: 13Px;
+        margin-right: 14Px;
+        background-color: #ffffff;
+        border-radius: 50%;
+        &:first-child{
+            background-color: #FFC121;
+        }
+        &:last-child{
+            margin-right: 0;
+        }
+        &.hide{
+            display: none;
+        }
     }
 }

+ 38 - 17
src/view/tick/index.tsx

@@ -8,8 +8,7 @@ import { browser } from "/src/utils/index";
 import tickWav from "/src/assets/tick.mp3";
 import tockWav from "/src/assets/tock.mp3";
 
-const browserInfo = browser();
-export const tickData = reactive({
+const tickData = reactive({
 	list: [] as number[],
 	len: 0,
 	tickEnd: false,
@@ -21,10 +20,18 @@ export const tickData = reactive({
 	index: 0,
 	show: false,
 });
-
+let _time: NodeJS.Timeout 
+// 关闭节拍器
+export function closeTick(){
+	if (tickData.show) {
+		_time && clearTimeout(_time)
+		tickData.tickEnd = true
+		tickData.show = false
+	}
+}
 const handlePlay = (i: number, source: any | null) => {
 	return new Promise((resolve) => {
-		setTimeout(() => {
+		_time=setTimeout(() => {
 			if (tickData.tickEnd) {
 				resolve(i)
 				return
@@ -33,7 +40,8 @@ const handlePlay = (i: number, source: any | null) => {
 			if (source) {
 				const beatVolume = state.setting.beatVolume / 100
 				source.volume = beatVolume;
-				if (source.volume <= 0) {
+				// 当mp3节拍器的时候不播放声音
+				if (source.volume <= 0 || state.isOpenMetronome || state.isSingOpenMetronome) {
 					source.muted = true
 				} else {
 					source.muted = false
@@ -75,6 +83,7 @@ export const handleInitTick = (beatLengthInMilliseconds: number, beat: number) =
 };
 
 /** 开始节拍器 */
+// 评测和练习模式,根据是否播放系统节拍器和mp3节拍器来控制是否发声,跟练模式百分之播
 export const handleStartTick = async () => {
 	tickData.show = true;
 	tickData.tickEnd = false;
@@ -108,10 +117,23 @@ export const handleStartTick = async () => {
 export default defineComponent({
 	name: "metronome",
 	setup() {
-		/** 手动关闭 */
-		const handleClose = () => {
-			tickData.tickEnd = true
-		};
+		const posObj = {
+			top: "0px",
+			left: "0px"
+		}
+		initPos()
+		function initPos() {
+			const musicAndSelectionDom = document.querySelector("#musicAndSelection")
+			const osmdSvgPage1Dom = musicAndSelectionDom?.querySelector("#osmdSvgPage1")
+			const stafflineDom = osmdSvgPage1Dom?.querySelector(".staffline")
+			const musicAndSelectionDomPos = musicAndSelectionDom?.getBoundingClientRect()
+			const osmdSvgPage1DomPos = osmdSvgPage1Dom?.getBoundingClientRect()
+			const stafflineDomPos = stafflineDom?.getBoundingClientRect()
+			Object.assign(posObj,{
+				top: (osmdSvgPage1DomPos?.top||0)-(musicAndSelectionDomPos?.top||0)-18+"px",
+				left: (stafflineDomPos?.left||0)-(osmdSvgPage1DomPos?.left||0)+"px"
+			})
+		}
 		onMounted(() => {
 			Promise.all([createAudio(tickWav), createAudio(tockWav)]).then(
 				([tick, tock]) => {
@@ -125,15 +147,14 @@ export default defineComponent({
 			);
 		});		
 		return () => (
-			<Popup class={[styles.popup, 'normal-close']} v-model:show={tickData.show} closeable onClickCloseIcon={handleClose}>
-				<div class={styles.dots}>
-					{Array(tickData.len)
-						.fill(0)
-						.map((n, i) => (
-							<div class={[styles.dot, tickData.index > i && styles.active, tickData.index > i && i === 0 && styles.one]}></div>
-						))}
+				tickData.show && 
+				<div class={styles.dots} style={posObj}>
+					{
+						Array.from({ length: tickData.len }).map((item,index)=>{
+							return <div class={[styles.dot,((tickData.len - tickData.index)<=index)&&styles.hide]}></div>
+						})
+					}
 				</div>
-			</Popup>
 		);
 	},
 });