瀏覽代碼

Merge branch 'hqyDevNewVersion' of http://git.dayaedu.com/liushengqiang/music-score into feature-tianyong-newVersion

TIANYONG 1 年之前
父節點
當前提交
bcfbb27d0b
共有 32 個文件被更改,包括 922 次插入249 次删除
  1. 11 4
      src/helpers/formateMusic.ts
  2. 3 3
      src/helpers/metronome.ts
  3. 二進制
      src/page-instrument/evaluat-model/countdown/imgs/step1.png
  4. 二進制
      src/page-instrument/evaluat-model/countdown/imgs/step2.png
  5. 二進制
      src/page-instrument/evaluat-model/countdown/imgs/step3.png
  6. 25 0
      src/page-instrument/evaluat-model/countdown/index.module.less
  7. 57 0
      src/page-instrument/evaluat-model/countdown/index.tsx
  8. 二進制
      src/page-instrument/evaluat-model/countdown/timer.mp3
  9. 3 1
      src/page-instrument/evaluat-model/index.tsx
  10. 159 65
      src/page-instrument/header-top/index.tsx
  11. 4 0
      src/page-instrument/header-top/settting/index.tsx
  12. 二進制
      src/page-instrument/view-detail/emptyMusic/imgs/empty.png
  13. 41 0
      src/page-instrument/view-detail/emptyMusic/index.module.less
  14. 46 0
      src/page-instrument/view-detail/emptyMusic/index.tsx
  15. 二進制
      src/page-instrument/view-detail/images/bg2.png
  16. 二進制
      src/page-instrument/view-detail/images/bg3.png
  17. 8 2
      src/page-instrument/view-detail/index.module.less
  18. 54 15
      src/page-instrument/view-detail/index.tsx
  19. 二進制
      src/page-instrument/view-detail/intonationLine/bird.png
  20. 31 0
      src/page-instrument/view-detail/intonationLine/index.less
  21. 343 0
      src/page-instrument/view-detail/intonationLine/index.tsx
  22. 13 3
      src/page-instrument/view-detail/smoothAnimation/index.ts
  23. 20 6
      src/state.ts
  24. 3 0
      src/view/evaluating/index.tsx
  25. 4 4
      src/view/music-score/index.module.less
  26. 二進制
      src/view/plugins/toggleMusicSheet/choosePartName/imgs/changeName.png
  27. 二進制
      src/view/plugins/toggleMusicSheet/choosePartName/imgs/okBtn.png
  28. 62 104
      src/view/plugins/toggleMusicSheet/choosePartName/index.module.less
  29. 32 29
      src/view/plugins/toggleMusicSheet/choosePartName/index.tsx
  30. 1 12
      src/view/plugins/toggleMusicSheet/index.module.less
  31. 1 1
      src/view/plugins/toggleMusicSheet/index.tsx
  32. 1 0
      src/view/tick/index.module.less

+ 11 - 4
src/helpers/formateMusic.ts

@@ -1,7 +1,6 @@
 import dayjs from "dayjs";
 import duration from "dayjs/plugin/duration";
 import state, { customData } from "/src/state";
-import { metronomeData as metronomeDataState } from "./metronome"
 import { browser } from "../utils/index";
 import {
 	isSpecialMark,
@@ -774,6 +773,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 	let multipleRestMeasures = 0;
 	let staveNoteIndex = 0;
 	let staveIndex = 0;
+	let xmlNoteTime = 0  // xml上面的音符时间
+	let xmlMp3BeatFixTime = 0 // xml上节拍器的时间
 
 	let preNoteEndTime = 0; // 上一个音符的结束时间
 
@@ -983,8 +984,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 					state.fixtime = fixtime;
 				}
 				// 存储mp3节拍器时间
-				metronomeDataState.xmlMp3BeatFixTime = getFixTime(beatSpeed)
-				// 
+				xmlMp3BeatFixTime = getFixTime(beatSpeed)
 				// console.log("fixtime:", fixtime, '速度:', beatSpeed, "state.isSpecialBookCategory:", state.isSpecialBookCategory, 'state.isOpenMetronome:', state.isOpenMetronome);
 			}
 			// console.log(getTimeByBeatUnit(beatUnit, measureSpeed, iterator.currentMeasure.activeTimeSignature.Denominator))
@@ -1210,7 +1210,14 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				measureSpeed,  // 小节速度
 				maxNoteNum: note.maxNoteNum, // 当前小节音符最多的分轨的音符数量
 				repeatIdx: iterator.repeatIdx || 0, // 标记是第几遍循环,从0开始
+				xmlNoteTime: retain(xmlNoteTime), // xml上音符开始时间 唱名用
+				xmlNoteEndTime: retain(xmlNoteTime + noteLength), //xml上音符结束时间 唱名用
+				xmlMp3BeatFixTime,  //xml上节拍器的时间
+				notBeatFixtime: state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime, // 不含节拍器的fixtime值 唱名用
+				notBeatTime: state.isEvxml && evNoteStartTime ? retain(evNoteStartTime) : retain(relativeTime + (state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime)), // 不含节拍器的 音符开始时间
+				notBeatEndTime: state.isEvxml && evNoteEndTime ? retain(evNoteEndTime) : retain(relaEndtime + (state.isOpenMetronome ? fixtime - xmlMp3BeatFixTime : fixtime)) // 不含节拍器的 音符结束时间
 			};
+			xmlNoteTime += noteLength
 			// 如果是妙极客的曲子,并且第二遍循环播放需要等待时间,并且是第二遍循环的第一个小节的第一个音符
 			// if (state.isEvxml && state.secondEvXmlBeginTime && nodeDetail.i > 0 && nodeDetail.MeasureNumberXML === 1 && nodeDetail.noteId === 0) {
 			// 	nodeDetail.time = nodeDetail.time + state.secondEvXmlBeginTime;
@@ -1256,7 +1263,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 		i++;
 	}
 	// 按照时间轴排序
-	const sortArray = allNotes.sort((a, b) => a.relativeTime - b.relativeTime).map((item, index) => ({ ...item, i: index }));
+	const sortArray = allNotes.sort((a, b) => a.relativeTime - b.relativeTime).map((item, index) => Object.assign(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("音符跑完时间");

+ 3 - 3
src/helpers/metronome.ts

@@ -42,7 +42,6 @@ export const metronomeData = reactive({
 	cursorTips: '' as string, // 光标模式提示文字
 	followAudioIndex: 1, // 当前的拍数
 	totalNumerator: 2, // 总拍数
-	xmlMp3BeatFixTime: 0   // 当前xml mp3节拍器的时间 切换演奏和演唱计算时间用
 });
 
 watch(
@@ -293,6 +292,7 @@ class Metronome {
 						numerator: note?.noteElement?.sourceMeasure?.ActiveTimeSignature?.numerator || 0,
 						start: note.measures[0].time,
 						end: note.measures[note.measures.length - 1].endtime,
+						// bug todo  弱起的时候 根据音符结尾时间减去音符开头时间,得到的不是正常小节的时间,然后平均分配节拍之后,当前节拍间隔会非常短
 						time: note.measures[note.measures.length - 1].endtime - note.measures[0].time,
 						stave_x: note?.noteElement?.sourceMeasure?.verticalMeasureList?.[0]?.stave?.x || 0,
 						end_x: note?.stave?.end_x || 0 || 0,
@@ -347,7 +347,7 @@ class Metronome {
 				}
 			}
 		}
-		console.log(measures, measures.length,'小节汇总');
+		//console.log(measures, measures.length,'小节汇总');
 
 		let metroList: number[] = [];
 		const metroMeasure: any[] = [];
@@ -385,7 +385,7 @@ class Metronome {
 		} catch (error) {
 			console.log(error);
 		}
-		// console.log('节拍器',metroList, metroMeasure);
+		console.log('节拍器',metroList, metroMeasure);
 		// 5.得到所有的节拍时间
 		metronomeData.metroList = metroList;
 		metronomeData.metroMeasure = metroMeasure;

二進制
src/page-instrument/evaluat-model/countdown/imgs/step1.png


二進制
src/page-instrument/evaluat-model/countdown/imgs/step2.png


二進制
src/page-instrument/evaluat-model/countdown/imgs/step3.png


+ 25 - 0
src/page-instrument/evaluat-model/countdown/index.module.less

@@ -0,0 +1,25 @@
+.countdown {
+    position: fixed;
+    left: calc(50% - 66px);
+    top: calc(50% - 66px);
+    width: 132px;
+    height: 132px;
+    z-index: 200;
+    background-size: 100% 100%;
+    background-repeat: no-repeat;
+    transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
+    opacity: 1;
+    &.step1{
+        background-image: url("./imgs/step1.png");
+    }
+    &.step2{
+        background-image: url("./imgs/step2.png");
+    }
+    &.step3{
+        background-image: url("./imgs/step3.png");
+    }
+    &.isAnimating {
+        transform: scale(0.5);
+        opacity: 0;
+    }
+}

+ 57 - 0
src/page-instrument/evaluat-model/countdown/index.tsx

@@ -0,0 +1,57 @@
+import { defineComponent, reactive, onMounted} from "vue"
+import styles from "./index.module.less"
+import soundWav from './timer.mp3';
+
+let soundVIdeo: HTMLAudioElement | undefined;
+
+const countdownData = reactive({
+	isShow: false,
+	step: 3,
+	isAnimating: false
+})
+let _countdownTIme: NodeJS.Timer
+
+export function startCountdown() {
+	Object.assign(countdownData, {
+		isShow: true,
+		step: 3,
+		isAnimating: false
+	})
+	soundVIdeo?.play()
+	let resolveFun: (value: boolean) => void
+	_countdownTIme = setInterval(() => {
+		if (countdownData.step <= 0) {
+			clearInterval(_countdownTIme)
+			countdownData.isShow = false
+			resolveFun(true)
+			soundVIdeo?.pause()
+		} else {
+			countdownData.isAnimating = true
+			const _time = setTimeout(() => {
+				clearTimeout(_time)
+				countdownData.isAnimating = false
+				countdownData.step--
+			}, 300)
+		}
+	}, 1000)
+	return new Promise( resolve => {
+		resolveFun = resolve
+	})
+}
+export default defineComponent({
+	name: "countdown",
+	setup() {
+		if(!soundVIdeo) {
+			soundVIdeo = new Audio(soundWav)
+			soundVIdeo.load();
+		}
+		onMounted(()=>{
+			soundVIdeo?.pause();
+		})
+		return () => (
+			<>
+				{countdownData.isShow && <div class={[styles.countdown,countdownData.isAnimating&&styles.isAnimating, styles[`step${countdownData.step}`]]}></div>}
+			</>
+		)
+	}
+})

二進制
src/page-instrument/evaluat-model/countdown/timer.mp3


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

@@ -21,6 +21,7 @@ import { api_musicPracticeRecordVideoUpload } from "../api";
 // import DelayCheck from "./delay-check";
 import { headTopData } from "../header-top/index";
 import { getQuery } from "/src/utils/queryString";
+import Countdown from "./countdown"
 
 const DelayCheck = defineAsyncComponent(() =>
   import('./delay-check')
@@ -400,7 +401,8 @@ export default defineComponent({
             onBack={() => handleDelayBack()}
           />
         )}
-
+        {/* 倒计时 */}
+        <Countdown />
         {/* 预加载延迟检测组建 */}
         {/* {evaluatingData.preloadJson && !evaluatingData.jsonLoadDone && (
             <div class={styles.preJson}>

+ 159 - 65
src/page-instrument/header-top/index.tsx

@@ -1,4 +1,4 @@
-import { Transition, computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch, toRef } from "vue";
+import { Transition, computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch, toRef,ComputedRef } from "vue";
 import styles from "./index.module.less";
 
 import iconBack from "./image/icon-back.png";
@@ -9,7 +9,7 @@ import { Badge, Circle, Popover, Popup, showConfirmDialog, showToast, NoticeBar
 import Speed from "./speed";
 import { evaluatingData, handleStartEvaluat } from "/src/view/evaluating";
 import Settting from "./settting";
-import state, { IPlatform, handleChangeSection, handleResetPlay, handleRessetState, togglePlay } from "/src/state";
+import state, { IPlatform, handleChangeSection, handleResetPlay, handleRessetState, togglePlay, IPlayState } from "/src/state";
 import { getAudioCurrentTime } from "/src/view/audio-list";
 import { followData, toggleFollow } from "/src/view/follow-practice";
 import { api_back } from "/src/helpers/communication";
@@ -89,6 +89,143 @@ export const headData = reactive({
   musicTypeShow: false,
 });
 
+let resetBtn:ComputedRef<{
+  display: boolean;
+  disabled: boolean;
+}>
+/**
+ * 处理模式切换
+ * @param oldPlayType   没改变之前的播放模式
+ * @param oldPlaySource  没改变之前的播放类型
+ * @param isforceReset   是否强制刷新播放状态 模式times时值改变时候也刷新
+ */
+export function handlerModeChange(oldPlayType:"play"|"sing", oldPlaySource:IPlayState,isforceReset?:boolean) {
+    const isModeChange = modeChangeHandleTimes(oldPlayType, oldPlaySource)
+    // 没有切换的时候 不处理下面的
+    if(isModeChange){
+      try {
+        metronomeData.metro.calculation(state.times);
+      } catch (error) {}
+      console.log("重新之后的times", state.times, state.fixtime)
+    }
+    if(isModeChange||isforceReset){
+      // 重置播放状态
+      handleRessetState()
+      // 隐藏重播按钮
+      resetBtn && (resetBtn.value.display = false)
+    }
+}
+// 模式切换之后重新给times赋值
+function modeChangeHandleTimes(oldPlayType:"play"|"sing", oldPlaySource:IPlayState){
+  const playType = state.playType
+  const playSource = state.playSource
+  const {notBeatFixtime, xmlMp3BeatFixTime} = state.times[0]
+  const { isOpenMetronome, isSingOpenMetronome } = state 
+  // 演奏向演唱切
+  if(oldPlayType === "play"&&playType === "sing"){
+    if(playSource === "mingSong"){
+      state.fixtime = 0
+      state.times.map(item => {
+        item.time = item.xmlNoteTime
+        item.endtime = item.xmlNoteEndTime
+        item.fixtime = 0
+      })
+      return true
+    }else{
+      //演奏开了节拍器,演唱没开节拍器
+      if(isOpenMetronome&&!isSingOpenMetronome){
+        state.fixtime = notBeatFixtime
+        state.times.map(item => {
+          item.time = item.notBeatTime
+          item.endtime = item.notBeatEndTime
+          item.fixtime = notBeatFixtime
+        })
+        return true
+      }else if(!isOpenMetronome&&isSingOpenMetronome){
+        state.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+        state.times.map(item => {
+          item.time = item.notBeatTime + xmlMp3BeatFixTime
+          item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime
+          item.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+        })
+        return true
+      }
+    }
+  }else if(oldPlayType === "sing"&&playType === "play"){
+    // 演唱向演奏切
+    if(oldPlaySource === "mingSong"){
+      // 有节拍器
+      if(isOpenMetronome){
+        state.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+        state.times.map(item => {
+          item.time = item.notBeatTime + xmlMp3BeatFixTime
+          item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime
+          item.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+        })
+        return true
+      }else{
+        state.fixtime = notBeatFixtime
+        state.times.map(item => {
+          item.time = item.notBeatTime
+          item.endtime = item.notBeatEndTime
+          item.fixtime = notBeatFixtime
+        })
+        return true
+      }
+    }
+    // 演奏开了节拍器,演唱没开节拍器
+    if(isOpenMetronome&&!isSingOpenMetronome){
+      state.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+      state.times.map(item => {
+        item.time = item.notBeatTime + xmlMp3BeatFixTime
+        item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime
+        item.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+      })
+      return true
+    }else if(!isOpenMetronome&&isSingOpenMetronome){
+      state.fixtime = notBeatFixtime
+      state.times.map(item => {
+        item.time = item.notBeatTime
+        item.endtime = item.notBeatEndTime
+        item.fixtime = notBeatFixtime
+      })
+      return true
+    }
+  }else if(oldPlayType === "sing"&&playType === "sing"){
+    // 演唱之间切换  
+    // 切到唱名时候
+    if(playSource === "mingSong"){
+      state.fixtime = 0
+      state.times.map(item => {
+        item.time = item.xmlNoteTime
+        item.endtime = item.xmlNoteEndTime
+        item.fixtime = 0
+      })
+      return true
+    }else if(oldPlaySource === "mingSong"){
+      // 有节拍器
+      if(isSingOpenMetronome){
+        state.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+        state.times.map(item => {
+          item.time = item.notBeatTime + xmlMp3BeatFixTime
+          item.endtime = item.notBeatEndTime + xmlMp3BeatFixTime
+          item.fixtime = notBeatFixtime + xmlMp3BeatFixTime
+        })
+        return true
+      }else{
+        state.fixtime = notBeatFixtime
+        state.times.map(item => {
+          item.time = item.notBeatTime
+          item.endtime = item.notBeatEndTime
+          item.fixtime = notBeatFixtime
+        })
+        return true
+      }
+    }
+  }
+  return false
+}
+
 export default defineComponent({
   name: "header-top",
   emits: ["close"],
@@ -200,6 +337,11 @@ export default defineComponent({
             // 原声, 伴奏 少一个,就不能切换
             if (state.music && state.accompany) return { display: true, disabled: false };
         } else {
+          // 播放过程中不能切换
+          if (state.playState === "play"){
+            return { display: true, disabled: true };
+          }
+          // 范唱 
           let index = 0
           state.fanSong && index++
           state.banSong && index++
@@ -223,15 +365,17 @@ export default defineComponent({
       // 音频播放中 禁用
       if (state.playState === "play") return { display: true, disabled: true };
       if (!state.isAppPlay) {
-        if(!state.isConcert){
           let index = 0
-          state.fanSong && index++
-          state.banSong && index++
-          state.mingSong && index++
-          if(index > 0) {
+          state.music && index++
+          state.accompany && index++
+          let songIndex = 0
+          state.fanSong && songIndex++
+          state.banSong && songIndex++
+          state.mingSong && songIndex++
+          // 演唱和演奏 都有数据的时间不禁用
+          if(songIndex>0&&index>0) {
             return { display: true, disabled: false };
           }
-        }
       }
       return {
         disabled: true,
@@ -266,7 +410,7 @@ export default defineComponent({
     });
 
     /** 重播按钮 */
-    const resetBtn = computed(() => {
+    resetBtn = computed(() => {
       // 选择模式 不显示
       if (headTopData.modeType !== "show") return { display: false, disabled: false };
       // 评测模式 不显示,跟练模式 不显示
@@ -428,61 +572,6 @@ export default defineComponent({
         console.log(e);
       }
     };
-
-    // 切换 演唱和演奏模式的时候处理
-    function handlerRefreshPlayType() {
-      // 妙极客的 曲子 时间是不变的,所以不做处理
-      if( !state.isEvxml ) {
-        // 重新计算state.times
-        const { isOpenMetronome, isSingOpenMetronome } = state
-        const { xmlMp3BeatFixTime } = metronomeData
-        if(state.playType === "play"){
-          if(isOpenMetronome && !isSingOpenMetronome){
-            state.fixtime = state.fixtime + xmlMp3BeatFixTime
-          } else if(!isOpenMetronome && isSingOpenMetronome){
-            state.fixtime = state.fixtime - xmlMp3BeatFixTime
-          }
-        }else{
-          if(isSingOpenMetronome && !isOpenMetronome){
-            state.fixtime = state.fixtime + xmlMp3BeatFixTime
-          } else if(!isSingOpenMetronome && isOpenMetronome){
-            state.fixtime = state.fixtime - xmlMp3BeatFixTime
-          }
-        }
-        const fixtime = state.fixtime
-        state.times.map(item => {
-          if(state.playType === "play"){
-            if(isOpenMetronome && !isSingOpenMetronome){
-              item.time = item.time + xmlMp3BeatFixTime
-              item.endtime = item.endtime + xmlMp3BeatFixTime
-              item.fixtime = fixtime
-            } else if(!isOpenMetronome && isSingOpenMetronome){
-              item.time = item.time - xmlMp3BeatFixTime
-              item.endtime = item.endtime - xmlMp3BeatFixTime
-              item.fixtime = fixtime
-            }
-          }else{
-            if(isSingOpenMetronome && !isOpenMetronome){
-              item.time = item.time + xmlMp3BeatFixTime
-              item.endtime = item.endtime + xmlMp3BeatFixTime
-              item.fixtime = fixtime
-            } else if(!isSingOpenMetronome && isOpenMetronome){
-              item.time = item.time - xmlMp3BeatFixTime
-              item.endtime = item.endtime - xmlMp3BeatFixTime
-              item.fixtime = fixtime
-            }
-          }
-        })
-        try {
-          metronomeData.metro.calculation(state.times);
-        } catch (error) {}
-        console.log("重新之后的times", state.times, fixtime)
-      }
-      // 重置播放状态
-      handleRessetState()
-      // 隐藏重播按钮
-      resetBtn.value.display = false
-    }
     return () => (
       <>
         <div
@@ -572,6 +661,8 @@ export default defineComponent({
               style={{ display: playTypeBtn.value.display ? "" : "none" }}
               class={[styles.btn, playTypeBtn.value.disabled && styles.disabled]}
               onClick={() => {
+                const oldPlayType = state.playType
+                const oldPlaySource = state.playSource
                 if(state.playType === "play"){
                   state.playType = "sing"
                   state.playSource = state.fanSong?"music":state.banSong?"background":"mingSong"
@@ -579,7 +670,7 @@ export default defineComponent({
                   state.playType = "play"
                   state.playSource = state.music?"music":"background"
                 }
-                handlerRefreshPlayType()
+                handlerModeChange(oldPlayType, oldPlaySource, true)
               }}
             >
               <img style={{ display: state.playType === "play" ? "" : "none" }} class={styles.iconBtn} src={headImg(`perform.png`)} />
@@ -591,6 +682,8 @@ export default defineComponent({
               style={{ display: originBtn.value.display ? "" : "none" }}
               class={[styles.btn, originBtn.value.disabled && styles.disabled]}
               onClick={() => {
+                const oldPlayType = state.playType
+                const oldPlaySource = state.playSource
                 if(state.playType === 'play'){
                   state.playSource = state.playSource === "music" ? "background" : "music";
                 }else{
@@ -602,6 +695,7 @@ export default defineComponent({
                     state.playSource = state.fanSong ? "music" :"background"
                   }
                 }
+                handlerModeChange(oldPlayType, oldPlaySource)
               }}
             >
               <img style={{ display: state.playSource === "music" ? "" : "none" }} class={styles.iconBtn} src={state.playType === 'play'?headImg(`music.png`):headImg(`music1.png`)} />

+ 4 - 0
src/page-instrument/header-top/settting/index.tsx

@@ -48,6 +48,10 @@ export default defineComponent({
                         <div class={styles.cellBox}>
                             <div class={styles.tit}>旋律线</div>
                             <Switch v-model={smoothAnimationState.isShow.value}></Switch>
+                        </div>                            
+                        <div class={styles.cellBox}>
+                            <div class={styles.tit}>延迟检测</div>
+                            <Switch v-model={state.setting.soundEffect}></Switch>
                         </div>                       
                         <div class={styles.cellBox}>
                             <div class={styles.tit}>标准音高</div>

二進制
src/page-instrument/view-detail/emptyMusic/imgs/empty.png


+ 41 - 0
src/page-instrument/view-detail/emptyMusic/index.module.less

@@ -0,0 +1,41 @@
+.emptyMusic {
+    position: fixed;
+    z-index: 9999;
+    width: 100vw;
+    height: 100vh;
+    top: 0;
+    left: 0;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background-color: #fff;
+    .emptyMusicBox{
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        .img{
+            width: 202px;
+            height: 179px;
+        }
+        .tit{
+            margin-top: 6px;
+            font-weight: 400;
+            font-size: 16px;
+            color: #999999;
+            line-height: 22px;
+        }
+        .backBtn{
+            margin-top: 16px;
+            width: 100px;
+            height: 36px;
+            border-radius: 18px;
+            border: 1px solid #1CACF1;
+            font-weight: 400;
+            font-size: 16px;
+            color: #1CACF1;
+            line-height: 36px;
+            text-align: center;
+            cursor: pointer;
+        }
+    }
+}

+ 46 - 0
src/page-instrument/view-detail/emptyMusic/index.tsx

@@ -0,0 +1,46 @@
+import { defineComponent, ref } from "vue"
+import styles from "./index.module.less"
+import empty from "./imgs/empty.png"
+import { browser } from "/src/utils";
+import { HANDLE_WORK_ADD } from "../../custom-plugins/work-index";
+import { storeData } from "/src/store";
+import { api_back } from "/src/helpers/communication";
+import { getQuery } from "/src/utils/queryString";
+
+export const isEmptyMusicShow = ref(false)
+export default defineComponent({
+   name: "emptyMusic",
+   setup() {
+      const query = getQuery();
+      const browInfo = browser()
+      /** 返回 */
+      const handleBack = () => {
+         HANDLE_WORK_ADD()
+         // 不在APP中,
+         if (!storeData.isApp) {
+            window.close()
+            return
+         }
+         if ((browInfo.iPhone || browInfo.ios) && query.workRecord) {
+            setTimeout(() => {
+               api_back()
+            }, 550)
+            return
+         }
+         api_back()
+      }
+      return () => (
+         <>
+            {isEmptyMusicShow.value && (
+               <div class={styles.emptyMusic}>
+                  <div class={styles.emptyMusicBox}>
+                     <img class={styles.img} src={empty} />
+                     <div class={styles.tit}>曲目已失效</div>
+                     <div class={styles.backBtn} onClick={handleBack}>返回</div>
+                  </div>
+               </div>
+            )}
+         </>
+      )
+   }
+})

二進制
src/page-instrument/view-detail/images/bg2.png


二進制
src/page-instrument/view-detail/images/bg3.png


+ 8 - 2
src/page-instrument/view-detail/index.module.less

@@ -20,6 +20,14 @@
     &.practise{
         background: url("./images/bg1.png") no-repeat;
         background-size: 100% 100%;
+    }    
+    &.follow{
+        background: url("./images/bg3.png") no-repeat;
+        background-size: 100% 100%;
+    }    
+    &.evaluating{
+        background: url("./images/bg2.png") no-repeat;
+        background-size: 100% 100%;
     }
 
     .headHeight {
@@ -39,7 +47,6 @@
         position: sticky;
         top: 36px;
         height: calc(100vh - 36px);
-        margin: 0 10px;
         border-radius: 10px;
         transition: height .2s;
         transition: padding-bottom .2s;
@@ -47,7 +54,6 @@
     }
     .pcContainer {
         height: calc(100vh - var(--header-height) - var(--pc-header-height));
-        margin: 0 24px;
     }
     .fingeringCon{
         transition: scale 0.2s;

+ 54 - 15
src/page-instrument/view-detail/index.tsx

@@ -9,7 +9,7 @@ import MusicScore from "../../view/music-score";
 import TestCheck from "/src/view/music-score/testCheck";
 import { sysMusicScoreAccompanimentQueryPage } from "../api";
 import EvaluatModel from "../evaluat-model";
-import HeaderTop from "../header-top";
+import HeaderTop, {handlerModeChange} from "../header-top";
 import styles from "./index.module.less";
 import { api_cloudAccompanyMessage, api_cloudLoading, api_keepScreenLongLight, api_openCamera, api_openWebView, api_setEventTracking, api_setRequestedOrientation, api_setStatusBarVisibility, isSpecialShapedScreen } from "/src/helpers/communication";
 import { getQuery } from "/src/utils/queryString";
@@ -35,6 +35,7 @@ import TheAudio from "/src/components/the-audio"
 import tickWav from "/src/assets/tick.mp3";
 import AuthorName from "../component/authorName"
 import { initSmoothAnimation } from "./smoothAnimation"
+import EmptyMusic, { isEmptyMusicShow } from "./emptyMusic"
 
 const DelayCheck = defineAsyncComponent(() =>
   import('/src/page-instrument/evaluat-model/delay-check')
@@ -144,7 +145,13 @@ export default defineComponent({
       // Promise.all([sysMusicScoreAccompanimentQueryPage(id)]).then((values) => {
       //   getMusicInfo(values[0]);
       // });
-      await getMusicDetail(id);
+      try { 
+        await getMusicDetail(id);
+      } catch (err) {
+        console.error(err)
+        isEmptyMusicShow.value = true
+        return
+      }
       detailData.isLoading = false;
       // 如果后台设置了不显示指法,关闭指法开关
       if (!state.isShowFingering) {
@@ -171,6 +178,9 @@ export default defineComponent({
       setCustomGradual();
 			setCustomNoteRealValue();
       state.times = formateTimes(osmd);
+      // state.times = resetFrequency(state.times);
+      state.times = setNoteHalfTone(state.times);
+      console.log("🚀 ~ state.times:", state.times, state.subjectId, state);
       // 一行谱
       if (state.isSingleLine) {
         // 音符添加位置信息bbox
@@ -178,9 +188,6 @@ export default defineComponent({
         // 一行谱创建 动画
         initSmoothAnimation();
       }
-      // state.times = resetFrequency(state.times);
-      state.times = setNoteHalfTone(state.times);
-      console.log("🚀 ~ state.times:", state.times, state.subjectId, state);
       // 初始化midi音频信息
       const songEndTime = state.times[state.times.length - 1 || 0]?.endtime || 0
       if (state.isAppPlay) {
@@ -196,18 +203,16 @@ export default defineComponent({
       // 需要向外面(iframe)派发计时器数据的时候触发
       if(query.isbeatTimes){
         const { isOpenMetronome, isSingOpenMetronome } = state
-        const { xmlMp3BeatFixTime } = metronomeData
+        const {xmlMp3BeatFixTime} = state.times[0] 
         const singBeatTime: number[][] = []
         const beatTime = metronomeData.metroMeasure.map(metroMeasure => {
           const singBeat:number[] = []
           const beatTimeItem = metroMeasure.map((item: any) => {
             let singTime = item.time
-            if(!state.isEvxml){
-              if(isSingOpenMetronome && !isOpenMetronome){
-                singTime += xmlMp3BeatFixTime
-              } else if(!isSingOpenMetronome && isOpenMetronome){
-                singTime -= xmlMp3BeatFixTime
-              }
+            if(isSingOpenMetronome && !isOpenMetronome){
+              singTime += xmlMp3BeatFixTime
+            } else if(!isSingOpenMetronome && isOpenMetronome){
+              singTime -= xmlMp3BeatFixTime
             }
             singBeat.push(singTime)
             return item.time
@@ -215,16 +220,48 @@ export default defineComponent({
           singBeatTime.push(singBeat)
           return beatTimeItem
         })
-        console.log("webApi_beatTimes",{beatTime,singBeatTime})
+        //改为唱名
+        state.fixtime = 0
+        state.times.map(item => {
+          item.time = item.xmlNoteTime
+          item.endtime = item.xmlNoteEndTime
+          item.fixtime = 0
+        })
+        metronomeData.metro.calculation(state.times)
+        const mingBeatTime:number[][] = metronomeData.metroMeasure.map(metroMeasure => {
+          const beatTimeItem = metroMeasure.map((item: any) => {
+            return item.time
+          })
+          return beatTimeItem
+        })
+        console.log("webApi_beatTimes",{beatTime,singBeatTime,mingBeatTime})
         window.parent.postMessage(
           {
             api: "webApi_beatTimes",
-            data: JSON.stringify({beatTime,singBeatTime})
+            data: JSON.stringify({beatTime,singBeatTime,mingBeatTime})
           },
           "*"
         );
+        throw new Error("webApi_beatTimes 完成");
+      }
+      // 根据当前文件有没有 设置当前的播放模式
+      if(!state.music){
+        if(state.accompany){
+          state.playSource = "background"
+        }else{
+          if(state.fanSong){
+            state.playType = "sing"
+            state.playSource = "music"
+          }else if(state.banSong){
+            state.playType = "sing"
+            state.playSource = "background"
+          }else if(state.mingSong){
+            state.playType = "sing"
+            state.playSource = "mingSong"
+          }
+          handlerModeChange("play", "music")
+        }
       }
-      
       /**
        * 2024.1.25
        * 设置节拍器,跟练需要播放系统节拍器,所以不需要判断needTick状态
@@ -435,6 +472,8 @@ export default defineComponent({
             </div>
           )}
         </Transition>
+        {/* 曲目加载错误的缺省 */}
+        <EmptyMusic></EmptyMusic>
         {/** 功能按钮 */}
         {
           !state.isPreView && 

二進制
src/page-instrument/view-detail/intonationLine/bird.png


+ 31 - 0
src/page-instrument/view-detail/intonationLine/index.less

@@ -0,0 +1,31 @@
+#musicAndSelection {
+    .intonationLineBox{
+        display: flex;
+        align-items: center;
+        height: 2.4rem;
+    }
+    .intonationLineCon{
+        position: relative;
+        border-bottom: 1px solid rgba(255, 255, 255, 0.12);
+        border-top: 1px solid rgba(255, 255, 255, 0.12);
+        .intonationLineCanvas{
+            display: block;
+        }
+    }
+    .intonationLineBot{
+        position: fixed;
+        background: url("./bird.png") no-repeat;
+        background-size: 100% 100%;
+        width: 24Px;
+        height: 30Px;
+        top: calc(36px + (90px - 80Px)/2 + 80Px);
+        margin-left: calc(-12Px + 10px);
+        margin-top: -30Px;
+    }
+    #osmdCanvasPage1{
+        top: 0;
+    }
+    #cursorImg-0 {
+        display: none;
+    }
+}

+ 343 - 0
src/page-instrument/view-detail/intonationLine/index.tsx

@@ -0,0 +1,343 @@
+import "./index.less"
+import state from "/src/state"
+import { getAudioCurrentTime } from "/src/view/audio-list"
+
+type rectNotesPosType = { x: number; y: number; width: number; MeasureNumberXML: number; frequency: number; noteId: number }[]
+type intonationLineType = {
+   canvasDom: null | HTMLCanvasElement
+   canvasCtx: null | undefined | CanvasRenderingContext2D
+   canvasDomWith: number
+   canvasDomHeight: number
+   intonationLineBoxDom: null | HTMLElement
+   osmdCanvasPageDom: null | HTMLElement
+   intonationLineBotDom: null | HTMLElement
+   musicLineDom: null | HTMLCanvasElement
+   rectNotesPos: rectNotesPosType
+   frequencyAv: number
+}
+
+export const intonationLineState = {
+   canvasDom: null,
+   canvasCtx: null,
+   canvasDomWith: 0,
+   canvasDomHeight: 80,
+   intonationLineBoxDom: null,
+   osmdCanvasPageDom: null,
+   intonationLineBotDom: null,
+   musicLineDom: null,
+   rectNotesPos: [], // 音符 音准线
+   frequencyAv: -1
+} as intonationLineType
+
+const progressMusicData = {
+   frequency: -1,
+   progressMusic: []
+} as {
+   frequency: number
+   progressMusic: { noteId: number; x: number; y: number; width: number; status: "start" | "ing" | "end" }[]
+}
+
+/**
+ * 初始化 音准器
+ */
+export function initIntonationLine() {
+   // 创建dom
+   createIntonationLine()
+   // 根据音符获取坐标
+   intonationLineState.rectNotesPos = getRectNotesPosByBatePos()
+   console.log("rectNotesPos:", intonationLineState.rectNotesPos)
+   const { musicLineDom } = drawMusicLine()
+   intonationLineState.musicLineDom = musicLineDom
+   // 画初始进度
+   drawProgressectMusic()
+   // 鸟的初始位置
+   intonationLineState.intonationLineBotDom && (intonationLineState.intonationLineBotDom.style.left = `${intonationLineState.rectNotesPos[0].x}px`)
+   console.log(intonationLineState, "评测小鸟数据")
+}
+
+//  模拟修改值
+setInterval(() => {
+   progressMusicData.frequency = getRandomNumberBetween(600, 800)
+}, 10)
+function getRandomNumberBetween(min: number, max: number) {
+   return Math.floor(Math.random() * (max - min + 1)) + min
+}
+
+/**
+ * 根据播放时间进度移动处理
+ */
+export function moveInitIntonationLineByPlayTime() {
+   const currentTime = getAudioCurrentTime()
+   if (currentTime <= state.fixtime) return
+   if (currentTime > state.times.last()?.endtime) return
+   // 当休止小节,可能当前音符在谱面上没有实际的音符(没有bbox),所以往后找谱面上有的音符
+   let nextIndex = state.activeNoteIndex + 1
+   let nextBBox = state.times[nextIndex]?.bbox
+   while (!nextBBox && nextIndex < state.times.length) {
+      nextIndex += 1
+      nextBBox = state.times[nextIndex]?.bbox
+   }
+   // 当前的音符和下一个音符之间的时值 (当是最后一个音符的时候,下一个音符的时间取当前音符的endtime)
+   const noteDuration =
+      (nextIndex > state.times.length - 1 ? state.times[state.activeNoteIndex]?.endtime : state.times[nextIndex].time) -
+      state.times[state.activeNoteIndex]?.time
+   // 当前时值在该区间的占比
+   const playProgress = (currentTime - state.times[state.activeNoteIndex]?.time) / noteDuration
+   // 当前的进度的位置信息
+   const noteDistance =
+      nextIndex > state.times.length - 1
+         ? intonationLineState.rectNotesPos[intonationLineState.rectNotesPos.length - 1].width
+         : state.times[nextIndex].bbox.x - state.times[state.activeNoteIndex].bbox.x
+   const affectDistance = noteDistance * playProgress
+   const translateXNum = affectDistance + state.times[state.activeNoteIndex].bbox.x - state.times[0].bbox.x
+   intonationLineState.osmdCanvasPageDom && (intonationLineState.osmdCanvasPageDom.style.transform = `translateX(-${translateXNum}px)`)
+   // 更新音准器小鸟的位置信息
+   updateBotByFrequency()
+   // 更新音准器数据
+   updateProgressMusicData(affectDistance)
+}
+/**
+ * 根据频率更新小鸟的位置信息
+ */
+function updateBotByFrequency() {
+   const frequency = progressMusicData.frequency
+   let translateYNum = 0
+   if (frequency !== -1) {
+      const y =
+         intonationLineState.canvasDomHeight / 2 -
+         (((frequency - intonationLineState.frequencyAv) / intonationLineState.frequencyAv) * intonationLineState.canvasDomHeight) / 2
+      if (y >= 0 && y <= intonationLineState.canvasDomHeight) {
+         translateYNum = intonationLineState.canvasDomHeight - y
+      } else if (y > intonationLineState.canvasDomHeight) {
+         translateYNum = 0
+      } else if (y < 0) {
+         translateYNum = intonationLineState.canvasDomHeight
+      }
+   }
+   intonationLineState.intonationLineBotDom && (intonationLineState.intonationLineBotDom.style.transform = `translateY(-${translateYNum}px)`)
+}
+
+/**
+ * 根据 频率更新音准器数据
+ * @affectDistance 偏移的距离
+ */
+const affectNum = 50
+function updateProgressMusicData(affectDistance: number) {
+   const { frequency, progressMusic } = progressMusicData
+   const { activeNoteIndex, times } = state
+   const activeNote = times[activeNoteIndex]
+   // 最后
+   const lastProgressmusic = progressMusic.length ? progressMusic[progressMusic.length - 1] : null
+   if (frequency + affectNum > activeNote.frequency && frequency - affectNum < activeNote.frequency) {
+      const y =
+         intonationLineState.canvasDomHeight / 2 -
+         (((activeNote.frequency - intonationLineState.frequencyAv) / intonationLineState.frequencyAv) * intonationLineState.canvasDomHeight) / 2
+      if (lastProgressmusic) {
+         // 有值的时候先看是不是当前小节
+         if (lastProgressmusic.noteId === activeNote.noteId) {
+            if (lastProgressmusic.status === "end") {
+               // 当前小节 新增
+               progressMusicData.progressMusic.push({
+                  noteId: activeNote.noteId,
+                  x: activeNote.bbox.x + affectDistance,
+                  y,
+                  width: 0,
+                  status: "start"
+               })
+            } else {
+               // 更新处理
+               lastProgressmusic.status = "ing"
+               lastProgressmusic.width = activeNote.bbox.x + affectDistance - lastProgressmusic.x
+            }
+         } else if (lastProgressmusic.status !== "end") {
+            // 不是当前小节 并且不为end时候
+            lastProgressmusic.status = "end"
+            // 下一个 小节 新增
+            progressMusicData.progressMusic.push({
+               noteId: activeNote.noteId,
+               x: activeNote.bbox.x + affectDistance,
+               y,
+               width: 0,
+               status: "start"
+            })
+         }
+      } else {
+         // 增加新值
+         progressMusicData.progressMusic.push({
+            noteId: activeNote.noteId,
+            x: activeNote.bbox.x + affectDistance,
+            y,
+            width: 0,
+            status: "start"
+         })
+      }
+   } else {
+      // 没有匹配上的时候 关闭最后一个
+      if (lastProgressmusic && lastProgressmusic.status !== "end") {
+         lastProgressmusic.status = "end"
+      }
+   }
+   // 更新音准器
+   drawProgressectMusic()
+}
+
+/**
+ * 画音准线进度
+ */
+function drawProgressectMusic() {
+   const canvasCtx = intonationLineState.canvasCtx!
+   const canvasDom = intonationLineState.canvasDom!
+   canvasCtx.clearRect(0, 0, canvasDom.width, canvasDom.height)
+   canvasCtx.drawImage(intonationLineState.musicLineDom!, 0, 0)
+   console.log(progressMusicData.progressMusic, 888)
+   // 过滤掉宽度为0的数据
+   const filterProgressMusic = progressMusicData.progressMusic.filter(item => {
+      return item.width !== 0
+   })
+   drawPathRectByArr(canvasCtx!, filterProgressMusic, "#FFC121")
+}
+
+/**
+ * 画音准线
+ */
+function drawMusicLine() {
+   const musicLineDom = document.createElement("canvas")
+   musicLineDom.width = intonationLineState.canvasDomWith
+   musicLineDom.height = intonationLineState.canvasDomHeight
+   const musicLineCtx = musicLineDom.getContext("2d")!
+   musicLineCtx.clearRect(0, 0, musicLineDom.width, musicLineDom.height)
+   // 画矩形
+   drawPathRectByArr(musicLineCtx, intonationLineState.rectNotesPos, "rgba(255, 255, 255, 0.3)")
+   return {
+      musicLineDom,
+      musicLineCtx
+   }
+}
+
+/**
+ * 创建dom
+ */
+function createIntonationLine() {
+   // osmdCanvasPage
+   const osmdCanvasPageDom = document.querySelector("#osmdCanvasPage1") as HTMLElement
+   intonationLineState.osmdCanvasPageDom = osmdCanvasPageDom
+   // box
+   const intonationLineBoxDom = document.createElement("div")
+   intonationLineBoxDom.className = "intonationLineBox"
+   intonationLineState.intonationLineBoxDom = intonationLineBoxDom
+   // con
+   const intonationLineConDom = document.createElement("div")
+   intonationLineConDom.className = "intonationLineCon"
+   //canvas
+   const intonationLineCanvasDom = document.createElement("canvas")
+   intonationLineCanvasDom.className = "intonationLineCanvas"
+   intonationLineState.canvasDom = intonationLineCanvasDom
+   intonationLineState.canvasDomWith = osmdCanvasPageDom?.offsetWidth | 0
+   intonationLineCanvasDom.width = intonationLineState.canvasDomWith
+   intonationLineCanvasDom.height = intonationLineState.canvasDomHeight
+   intonationLineState.canvasCtx = intonationLineCanvasDom.getContext("2d")
+   // bot
+   const intonationLineBotDom = document.createElement("div")
+   intonationLineBotDom.className = "intonationLineBot"
+   intonationLineState.intonationLineBotDom = intonationLineBotDom
+   document.querySelector("#musicAndSelection")?.appendChild(intonationLineBotDom)
+   intonationLineConDom.appendChild(intonationLineCanvasDom)
+   intonationLineBoxDom.appendChild(intonationLineConDom)
+   // 添加到 osmdCanvasPage1
+   osmdCanvasPageDom?.insertBefore(intonationLineBoxDom, osmdCanvasPageDom.firstChild)
+}
+
+/**
+ * 根据音符获取坐标
+ */
+function getRectNotesPosByBatePos(): rectNotesPosType {
+   let totalAvInde = 0
+   // 取平均值
+   const totalAv =
+      state.times.reduce((total, item) => {
+         if (item.frequency !== -1) {
+            // -1 为休止符
+            total += item.frequency
+            totalAvInde++
+         }
+         return total
+      }, 0) / totalAvInde
+   intonationLineState.frequencyAv = totalAv
+   const notesPos = state.times.reduce((notesArr: any[], item) => {
+      // 当休止小节,可能当前音符在谱面上没有实际的音符(没有bbox),所以往后找谱面上有的音符
+      if (item.bbox) {
+         notesArr.push({
+            noteId: item.noteId,
+            frequency: item.frequency,
+            MeasureNumberXML: item.MeasureNumberXML,
+            x: item.bbox.x,
+            // 当为休止符的时候 取最下面的位置*0.9,确保能显示完整
+            y:
+               intonationLineState.canvasDomHeight / 2 -
+               ((((item.frequency === -1 ? 2 * totalAv * 0.1 : item.frequency) - totalAv) / totalAv) * intonationLineState.canvasDomHeight) / 2
+            // cavans 高度为160 所以基准为80
+         })
+      }
+      return notesArr
+   }, [])
+   // 最后一个音符延长(这里建立一个虚拟的音符延长)
+   const extendPoint = {
+      ...notesPos[notesPos.length - 1]
+   }
+   extendPoint.MeasureNumberXML++
+   extendPoint.frequency = -1
+   // 当总长度减30小于最后一个音符时候,取最后一个音符加15
+   extendPoint.x = intonationLineState.canvasDomWith - 30 > extendPoint.x ? intonationLineState.canvasDomWith - 30 : extendPoint.x + 15
+   notesPos.push(extendPoint)
+   /* 计算音准线 */
+   return notesPos.reduce(
+      (
+         arr: { MeasureNumberXML: number; frequency: number; x: number; y: number; width: number; noteId: number }[],
+         { frequency, x, y, MeasureNumberXML, noteId },
+         index
+      ) => {
+         const canvasDataLen = notesPos.length
+         if (index < canvasDataLen - 1) {
+            const nextCanvasObj = notesPos[index + 1]
+            arr.push({
+               noteId,
+               MeasureNumberXML,
+               frequency,
+               x,
+               y,
+               width: nextCanvasObj.x - x
+            })
+         }
+         return arr
+      },
+      []
+   )
+}
+
+/**
+ * 根据坐标信息 画矩形路径
+ */
+function drawPathRectByArr(ctx: CanvasRenderingContext2D, arr: { x: number; y: number; width: number }[], color: string) {
+   arr.map(({ x, y, width }) => {
+      drawRect(ctx, x, y, width * 0.92, 7, 4, color)
+   })
+}
+
+/**
+ * 画矩形
+ */
+function drawRect(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, radius: number, color: string) {
+   ctx.fillStyle = color
+   ctx.beginPath()
+   ctx.moveTo(x + radius, y)
+   ctx.lineTo(x + width - radius, y)
+   ctx.arcTo(x + width, y, x + width, y + radius, radius)
+   ctx.lineTo(x + width, y + height - radius)
+   ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius)
+   ctx.lineTo(x + radius, y + height)
+   ctx.arcTo(x, y + height, x, y + height - radius, radius)
+   ctx.lineTo(x, y + radius)
+   ctx.arcTo(x, y, x + radius, y, radius)
+   ctx.closePath()
+   ctx.fill()
+}

+ 13 - 3
src/page-instrument/view-detail/smoothAnimation/index.ts

@@ -113,6 +113,10 @@ export function destroySmoothAnimation() {
  * 根据播放时间进度移动处理
  */
 export function moveSmoothAnimationByPlayTime() {
+   // 暂停之后不进行移动了
+   if (state.playState === "paused") {
+      return
+   }
    const currentTime = getAudioCurrentTime()
    if (currentTime <= state.fixtime) return
    if (currentTime > state.times.last()?.endtime) return
@@ -172,7 +176,7 @@ export function moveSmoothAnimation(progress: number, activeIndex: number) {
    )
    // 当移动到屏幕最右边时候 就不进行移动了
    if (
-      (smoothAnimationState.osdmScrollDom?.scrollLeft || 0) + smoothAnimationState.translateXNum + smoothAnimationState.osdmScrollDomWith >
+      (smoothAnimationState.osdmScrollDom?.scrollLeft || 0) + smoothAnimationState.translateXNum + smoothAnimationState.osdmScrollDomWith >=
       smoothAnimationState.canvasDomWith
    ) {
       return
@@ -231,6 +235,12 @@ function move_osmd(nowPointsPos: pointsPosType[0]) {
    } else if (midBotNum > clientMidWidth + clientWidth * 0.45 && midBotNum <= clientMidWidth + clientWidth * 0.5) {
       smoothAnimationState.translateXNum += speed * 5
    }
+   // 最多移动的位置
+   const maxTranslateXNum =
+      smoothAnimationState.canvasDomWith - smoothAnimationState.osdmScrollDomWith - (smoothAnimationState.osdmScrollDom?.scrollLeft || 0)
+   if (smoothAnimationState.translateXNum > maxTranslateXNum) {
+      smoothAnimationState.translateXNum = maxTranslateXNum
+   }
    moveTranslateXNum(smoothAnimationState.translateXNum)
 }
 
@@ -321,8 +331,8 @@ function getPointsPosByBatePos(): pointsPosType {
             x: item.bbox.x,
             // 当为休止符的时候 取最下面的位置*0.9,确保能显示完整
             y:
-               ((((item.frequency === -1 ? 2 * totalAv * 0.9 : item.frequency) - totalAv) / totalAv) * smoothAnimationState.canvasDomHeight) / 2 +
-               smoothAnimationState.canvasDomHeight / 2 // cavans 高度为160 所以基准为80
+               smoothAnimationState.canvasDomHeight / 2 -
+               ((((item.frequency === -1 ? 2 * totalAv * 0.1 : item.frequency) - totalAv) / totalAv) * smoothAnimationState.canvasDomHeight) / 2
          })
       }
       return posArr

+ 20 - 6
src/state.ts

@@ -1209,7 +1209,6 @@ const getMusicInfo = async (res: any) => {
   index = tracks.findIndex(item => {  // 筛选出当前的index
     return item === musicObj?.track
   })
-  index = index >= 0 ? index : 0;
   const musicInfo = {
     ...res.data,
     track: musicObj?.track
@@ -1242,6 +1241,10 @@ function initMusicSource (data: any, track?:string) {
     musicObj = musicSheetSoundList.find((item:any) => {
       return (isAllSubject||!instrumentId)?item.audioPlayType === "PLAY":(item.audioPlayType === "PLAY"&&item.musicalInstrumentId ==instrumentId)
     })
+    // 因为可能根据学生的乐器id也找不到曲目所以尝试取第一个
+    musicObj || (musicObj = musicSheetSoundList.find((item:any) => {
+      return item.audioPlayType === "PLAY"
+    }))
     fanSongObj = musicSheetSoundList.find((item:any) => {
       return item.audioPlayType === "SING"
     })
@@ -1257,6 +1260,10 @@ function initMusicSource (data: any, track?:string) {
   accompanyObj = musicSheetAccompanimentList.find((item:any) => {
     return item.audioPlayType === "PLAY"
   })
+  // 当没有任何曲目的时候报错
+  if(!musicObj?.audioFileUrl&&!accompanyObj?.audioFileUrl&&!fanSongObj?.audioFileUrl&&!banSongObj?.audioFileUrl&&!fanSongObj?.solmizationFileUrl){
+    throw new Error("该曲目无任何音源");
+  }
   Object.assign(state, {
     music: musicObj?.audioFileUrl,
     accompany: accompanyObj?.audioFileUrl,
@@ -1377,11 +1384,6 @@ const setState = (data: any, index: number) => {
     state.setting.displayFingering = false
   }
 
-  // 检测是否原音和伴奏都有
-  if (!state.music || !state.accompany) {
-    state.playSource = state.music ? "music" : "background";
-  }
-
   // 如果是PC端,放大曲谱
   state.platform = query.platform?.toLocaleUpperCase() || "";
   if (state.platform === IPlatform.PC) {
@@ -1706,3 +1708,15 @@ export const refreshMusicSvg = () => {
   }
   musicScoreRef.value?.refreshMusicScore()
 }
+
+// 指法改变显示的时候 osdmScrollDomWith 宽度会变化 所以指法改变的时候这个宽度重新计算
+watch(
+  () => state.setting.displayFingering,
+  () => {
+    nextTick(()=>{
+      if (smoothAnimationState.osdmScrollDom) {
+        smoothAnimationState.osdmScrollDomWith = smoothAnimationState.osdmScrollDom.offsetWidth | 0
+      }
+    })
+  }
+)

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

@@ -53,6 +53,7 @@ import icon_close from '../abnormal-pop/icon_close.svg'
 import icon_btn from '../abnormal-pop/icon_btn.svg'
 import icon_success from '../abnormal-pop/icon_success.svg'
 import { data } from '../../page-instrument/custom-plugins/work-index'
+import { startCountdown } from "/src/page-instrument/evaluat-model/countdown"
 
 const browserInfo = browser();
 
@@ -363,6 +364,8 @@ export const handleStartBegin = async (preTimes?: number) => {
 	}
 	evaluatingData.startBegin = true;
 	if (evaluatingData.isDisabledPlayMusic) {
+		// 先播放倒计时
+		await startCountdown()
 		state.playState = state.playState === "paused" ? "play" : "paused";
 		// 设置为开始播放时, 如果需要节拍,先播放节拍器
 		if (state.playState === "play" && (state.playType==="play"&&state.needTick)||(state.playType==="sing"&&state.needSingTick)) {

+ 4 - 4
src/view/music-score/index.module.less

@@ -12,10 +12,10 @@
             width: 0;
             display: none;
         }
-        & > div {
-            transform: scale(var(--music-zoom));
-            transform-origin: left top;
-        }
+        // & > div {
+        //     transform: scale(var(--music-zoom));
+        //     transform-origin: left top;
+        // }
         #osmdCanvasPage1{
             position: absolute !important;
             left: 0;

二進制
src/view/plugins/toggleMusicSheet/choosePartName/imgs/changeName.png


二進制
src/view/plugins/toggleMusicSheet/choosePartName/imgs/okBtn.png


+ 62 - 104
src/view/plugins/toggleMusicSheet/choosePartName/index.module.less

@@ -1,119 +1,77 @@
 .container {
-  display: flex;
-  flex-direction: column;
-  width: 50vw;
-  height: 80vh;
-  max-height: 500px;
-  background-color: #fff;
-  overflow: hidden;
-
-  .top {
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    padding: 8px 10px;
-    position: relative;
-    font-size: 14Px;
-    z-index: 999;
-    &::before {
-      content: '';
-      position: absolute;
-      left: 4px;
-      top: 50%;
-      height: 30%;
-      transform: translateY(-50%);
-      width: 2px;
-      background-color: var(--van-primary-color);
-      border-radius: 2px;
-    }
-  }
-
-  .closeIcon{
-    width: 15px;
-    height: 15px;
-    margin: 0 10px;
-    position: relative; 
-    cursor: pointer;
-  }
-  .closeIcon::before,
-  .closeIcon::after{
-      content: "";
-      position: absolute;
-      height: 15px;
-      width: 1.5px;
-      top: 4px;
-      right: 9px;
-      background: #000;
+  .head {
+      background: url("../../../../page-instrument/header-top/image/headImg.png") no-repeat;
+      background-size: 100% 100%;
+      width: 372px;
+      height: 57px;
+      position: relative;
+      .headTit{
+          position: absolute;
+          bottom: 9px;
+          left: 50%;
+          transform: translateX(-50%);
+          width: 38px;
+          height: 18px;
+      }        
+      .closeImg{
+          position: absolute;
+          top: 0;
+          right: -38px;
+          width: 32px;
+          height: 32px;
+          cursor: pointer;
+      }
   }
-  .closeIcon::before{
-      transform: rotate(45deg);
+  .pickerCon{
+    width: 354px;
+    height: 284px;
+    background: #B0D8FF;
+    box-shadow: 0px 4px 0px 0px #7AAEE0;
+    border-radius: 0px 0px 24px 24px;
+    margin: 0 auto;
+    padding: 10px;
+    .pickerBox{
+      width: 100%;
+      height: 100%;
+      background: #EAF2FB;
+      border-radius: 12px;
+    }
   }
-  .closeIcon::after{
-      transform: rotate(-45deg);
-  }   
-
   .picker {
-    flex: 1;
-    height: 100px;
+    height: 204px;
+    overflow: hidden;
+    background-color: initial;
     display: flex;
     align-items: center;
-    justify-content: center;
-    :global {
-      .van-picker__columns {
-        //height: 80% !important;
+    :global{
+      .van-picker__mask {
+        background-image: initial;
       }
-    }
-    &.pcPicker {
-      :global {
-        .van-ellipsis {
-          font-size: 20PX;
-        }
-        .van-picker-column__item {
-          height: 60px;
+      .van-picker__columns{
+        width: 100%;
+      }
+      .van-picker-column__wrapper{
+        padding: 0 16px;
+      }
+      .van-picker-column__item{
+        font-weight: 600;
+        font-size: 15px;
+        color: rgba(0,0,0,0.2);
+        &.van-picker-column__item--selected{
+          border-top: 1px solid #D5E0ED;
+          border-bottom: 1px solid #D5E0ED;
+          color: rgb(0,0,0);
         }
       }
     }
   }
-
   .button {
-    width: 50%;
-    height: 40Px;
-    margin: 10Px auto;
-    position: relative;
+    cursor: pointer;
+    width: 118px;
+    height: 40px;
+    margin: 10px auto 0;
     z-index: 9;
-  }
-  &.pcContainer {
-    width: 500PX;
-    height: 380PX;
-    border-radius: 16PX;
-    .button {
-      width: 40%;
-      margin-bottom: 30PX;
-    }
-    .title {
-      font-size: 20PX;
-    }
-    .closeIcon {
-      margin: initial !important;
-      top: -6PX;
-    }
-    .top {
-      padding-left: 30PX;
-      &::before { 
-        left: 20PX;
-      }
-    }
-    :global {
-      .van-button__content {
-        font-size: 20PX;
-      }
-    }
-  }
-}
-.pcPartTop {
-  z-index: 9999;
-  height: 12px;
-  &.pcPartTopZIndex {
-    z-index: 1;
+    background: url("./imgs/okBtn.png") no-repeat;
+    background-size: 100% 100%;
   }
 }

+ 32 - 29
src/view/plugins/toggleMusicSheet/choosePartName/index.tsx

@@ -2,6 +2,8 @@ import { PropType, computed, defineComponent, ref, toRefs, onMounted } from 'vue
 import { Picker, Button, Icon } from 'vant'
 import styles from './index.module.less'
 import state, { IPlatform } from "/src/state";
+import changeName from "./imgs/changeName.png"
+import { headImg } from "/src/page-instrument/header-top/image";
 
 export default defineComponent({
   name: 'choosePartName',
@@ -38,37 +40,38 @@ export default defineComponent({
 		});
     return () => (
       <div class={[styles.container, state.platform === IPlatform.PC && styles.pcContainer]}>
+        <div class={styles.head}>
+          <img class={styles.headTit} src={changeName} />
+          <img class={styles.closeImg} src={headImg("closeImg.png")} onClick={() => emit("close")} />
+        </div>
         { state.platform === IPlatform.PC && <div class={[!state.guideInfo?.teacherDrag && styles.pcPartTopZIndex ,styles.pcPartTop,'top_drag']}></div> }
-        <div class={styles.top}>
-          <div class={styles.title}>请选择您练习的乐器</div>
-          {/* <Icon name="cross" size={24} onClick={() => emit('close')} /> */}
-          <span class={styles.closeIcon} onClick={() => emit("close")}></span>
+        <div class={styles.pickerCon}>
+          <div class={styles.pickerBox}>
+            <Picker
+              ref={myPicker}
+              class={[styles.picker, state.platform === IPlatform.PC && styles.pcPicker]}
+              defaultIndex={props.partIndex}
+              v-model={selValues.value}
+              showToolbar={false}
+              columns={columns.value}
+              visibleItemCount={Math.ceil(document.body.clientHeight / 40 / 3)}
+              onChange={(row) => {
+                // console.log(1111,'选择的索引', row)
+                if (!partIndexChanged.value) partIndexChanged.value = true
+                selectIndex.value = row.selectedValues[0]
+              }}
+            />
+            <div class={styles.button} onClick={() => {
+                // console.log(1111,selectIndex.value)
+                if (partIndexChanged.value) {
+                  emit('close', selectIndex.value)
+                } else {
+                  emit('close', partIndex.value)
+                }
+              }
+            }></div>
+          </div>
         </div>
-        <Picker
-          ref={myPicker}
-          class={[styles.picker, state.platform === IPlatform.PC && styles.pcPicker]}
-          defaultIndex={props.partIndex}
-          v-model={selValues.value}
-          showToolbar={false}
-          columns={columns.value}
-          visibleItemCount={Math.ceil(document.body.clientHeight / 44 / 3)}
-          onChange={(row) => {
-            // console.log(1111,'选择的索引', row)
-            if (!partIndexChanged.value) partIndexChanged.value = true
-            selectIndex.value = row.selectedValues[0]
-          }}
-        />
-        <Button class={styles.button} type="primary" round block onClick={() => {
-            // console.log(1111,selectIndex.value)
-            if (partIndexChanged.value) {
-              emit('close', selectIndex.value)
-            } else {
-              emit('close', partIndex.value)
-            }
-          }
-        }>
-          确定
-        </Button>
       </div>
     )
   },

+ 1 - 12
src/view/plugins/toggleMusicSheet/index.module.less

@@ -1,16 +1,5 @@
-.iconToggle {
-    position: fixed;
-    left: 0;
-    bottom: 20%;
-    transform: scaleX(-1);
-    z-index: 10;
-    margin: 0;
-    background: rgba(0, 0, 0, 0.24);
-    border-radius: 5px 0px 0px 5px;
-    padding: 7px 3px;
-}
+
 .popup{
-    border-radius: 4px;
     overflow: hidden;
 }
 .pcPartPop {

+ 1 - 1
src/view/plugins/toggleMusicSheet/index.tsx

@@ -107,7 +107,7 @@ export default defineComponent({
     }
 
     return () => (
-      <Popup class={styles.popup} v-model:show={toggleMusicSheet.show} class={[state.platform === IPlatform.PC && styles.pcPartPop ,"switchBoxClass_drag"]} style={positionInfo.styleDrag.value}>
+      <Popup v-model:show={toggleMusicSheet.show} class="popup-custom van-scale center-closeBtn switchBoxClass_drag" transition="van-scale" teleport="body" style={positionInfo.styleDrag.value} overlay-style={{background:'rgba(0, 0, 0, 0.3)'}}>
         <ChoosePartName
           partIndex={trackIdx.value || 0}
           partListNames={partListNames.value}

+ 1 - 0
src/view/tick/index.module.less

@@ -4,6 +4,7 @@
     align-items: center;
     position: absolute;
     transform: translateY(-100%);
+    z-index: 1;
     .dot{
         width: 13Px;
         height: 13Px;