소스 검색

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

TIANYONG 1 년 전
부모
커밋
603dc384b9

+ 1 - 0
src/helpers/customMusicScore.ts

@@ -540,6 +540,7 @@ export const resetFormate = () => {
 				Array.from(stave?.querySelectorAll(".vf-Volta") || []),
 				Array.from(stave?.querySelectorAll(".vf-clef") || []),
 				Array.from(stave?.querySelectorAll(".vf-keysignature") || []),
+				Array.from(stave?.querySelectorAll(".vf-Repetition") || []),
 				Array.from(stave?.getElementsByTagName("text") || []),
 			].flat();
 			try {

+ 6 - 1
src/helpers/formateMusic.ts

@@ -1207,6 +1207,11 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			}
 			svgElement?.attrs.id && noteIds.push(svgElement?.attrs.id)
 
+			// 如果该音符包含倚音,添加标记
+			let hasGraceNote = false;
+			if (svgElement?.modifiers?.length) {
+				hasGraceNote = svgElement?.modifiers.some((item: any) => item?.attrs?.type === "GraceNoteGroup")
+			}
 			const nodeDetail = {
 				isStaccato: note.voiceEntry.isStaccato(),
 				isRestFlag: note.isRestFlag,
@@ -1231,6 +1236,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				measureLength,
 				relaMeasureLength,
 				id: svgElement?.attrs.id,
+				hasGraceNote,
 				note: note.halfTone + 12, // see issue #224
 				fixtime, // 弱起补充的时间
 				relativeTime: retain(relativeTime),
@@ -1314,7 +1320,6 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 	try {
 		osmd.cursor.reset();
 	} catch (error) {}
-	state.activeMeasureIndex = sortArray[0].MeasureNumberXML;
 	return sortArray;
 };
 

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

@@ -219,6 +219,12 @@ export default defineComponent({
           measureIndex++;
           recordMeasure = note.measureOpenIndex;
         }
+        // 是否是需要延续、不停顿演奏的音符
+        let isTenutoSound = false;
+        if (item?.noteElement?.tie && item.noteElement.tie?.StartNote) {
+          const startId = item.noteElement.tie?.StartNote?.NoteToGraphicalNoteObjectId
+          isTenutoSound = item.NoteToGraphicalNoteObjectId === startId ? false : true
+        }
         const data = {
           timeStamp: (start * 1000) / rate,
           duration: ((end * 1000) / rate - (start * 1000) / rate) * noteRate,
@@ -228,10 +234,12 @@ export default defineComponent({
           // 重复的情况index会自然累加,render的index是谱面渲染的index
           measureIndex: measureIndex,
           measureRenderIndex: item.measureListIndex,
-          dontEvaluating: ListenMode || dontEvaluatingMode || item.skipMode,
+          dontEvaluating: item.hasGraceNote || ListenMode || dontEvaluatingMode || !!item?.voiceEntry?.ornamentContainer || !!item.noteElement?.speedInfo?.startWord?.includes('rit.') || item.skipMode,
           musicalNotesIndex: index,
           denominator: note.noteElement?.Length.denominator,
-          isOrnament: !!note?.voiceEntry?.ornamentContainer,
+          // isOrnament: !!note?.voiceEntry?.ornamentContainer,
+          isTenutoSound,
+          isStaccato: item?.voiceEntry?.isStaccato, // 是否是重音
         };
         datas.push(data);
       }

+ 7 - 1
src/page-instrument/header-top/index.module.less

@@ -7,6 +7,9 @@
     margin-left: calc(-1 * var(--detailDataPaddingLeft));
     padding: 0 30px;
     background: linear-gradient( 180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.6) 100%);
+    &.headerTopRight {
+        justify-content: flex-end;
+    }
 }
 .modeWarn{
     position: fixed;
@@ -30,6 +33,9 @@
         width: 18px;
         height: 18px;
     }
+    &.modeWarnRight {
+        right: 30px;
+    }
 }
 .headTopLeftBox{
     position: fixed;
@@ -212,7 +218,7 @@
     }
 
     &.pauseLeftButton {
-        left: 88px !important;
+        left: 108px !important;
         right: auto !important;
         bottom: 12px !important;
     }

+ 53 - 72
src/page-instrument/header-top/index.tsx

@@ -589,7 +589,7 @@ export default defineComponent({
     return () => (
       <>
         <div
-          class={[styles.headerTop]}
+          class={[styles.headerTop, state.platform === IPlatform.PC && state.musicScoreBtnDirection === "left" ? styles.headerTopRight : ""]}
           onClick={(e: Event) => {
             e.stopPropagation();
             if (state.platform === IPlatform.PC) {
@@ -604,53 +604,51 @@ export default defineComponent({
           }}
         >
           {/* 返回和标题 */}
-          {!(state.playState == "play" || followData.start || evaluatingData.startBegin) && (
-            <div class={styles.headTopLeftBox}>
-              <img src={iconBack} class={["headTopBackBtn", styles.img, !headTopData.showBack && styles.hidenBack]} onClick={handleBack} />
-              {smoothAnimationState.isShow.value ? (
-                <div
-                  class={[styles.title, isMusicList.value && styles.isMusicList, "driver-8"]}
-                  onClick={() => {
-                    isMusicList.value && (musicListShow.value = true);
-                  }}
-                >
-                  <NoticeBar text={state.examSongName} background="none" />
-                </div>
-              ) : (
-                isMusicList.value && (
-                  <img
-                    src={listImg}
-                    class={[styles.img, "driver-8"]}
-                    onClick={() => {
-                      musicListShow.value = true;
-                    }}
-                  />
-                )
-              )}
-            </div>
-          )}
+          {
+            !(state.playState == "play" || followData.start || evaluatingData.startBegin) &&
+              <div class={styles.headTopLeftBox}>
+                <img src={iconBack} class={['headTopBackBtn', styles.img, !headTopData.showBack && styles.hidenBack]} onClick={handleBack} />
+                {
+                  smoothAnimationState.isShow.value ?
+                    <div class={[styles.title,isMusicList.value && styles.isMusicList, "driver-8"]} onClick={()=>{
+                        isMusicList.value && (musicListShow.value = true)
+                      }}>
+                        <NoticeBar
+                          text={state.examSongName}
+                          background="none"
+                        />
+                    </div> :
+                    isMusicList.value &&
+                    <img src={listImg} class={[styles.img, "driver-8"]} onClick={()=>{
+                      musicListShow.value = true
+                    }} />
+                }
+              </div>
+          }
           {/* 模式切换 */}
-          {state.playType === "play" && (
-            <div
-              id={state.platform === IPlatform.PC ? "teacherTop-0" : "studnetT-0"}
-              style={{ display: toggleBtn.value.display ? "" : "none" }}
-              class={["driver-9", styles.modeChangeBox, toggleBtn.value.disabled && styles.disabled]}
-              onClick={() => {
-                handleRessetState();
-                headTopData.modeType = "init";
-              }}
-            >
-              <img class={styles.img} src={iconMode} />
-              <div class={styles.title}>{state.modeType === "practise" ? "练习模式" : state.modeType === "follow" ? "跟练模式" : state.modeType === "evaluating" ? "评测模式" : ""}</div>
-            </div>
-          )}
+          { 
+            state.playType === "play" &&
+              <div 
+                id={state.platform === IPlatform.PC ? "teacherTop-0" : "studnetT-0"}
+                style={{ display: toggleBtn.value.display ? "" : "none" }}
+                class={["driver-9", styles.modeChangeBox, toggleBtn.value.disabled && styles.disabled]} 
+                onClick={() => {
+                    handleRessetState();
+                    headTopData.modeType = "init";
+                }}
+              >
+                <img class={styles.img} src={iconMode} />
+                <div class={styles.title}>{state.modeType==="practise" ? '练习模式' : state.modeType==="follow" ? "跟练模式" : state.modeType==="evaluating" ? "评测模式" : ""}</div>
+              </div>
+          }
           {/* 模式提醒 */}
-          {state.modeType === "practise" && (
-            <div class={[styles.modeWarn, "practiseModeWarn"]}>
-              <img src={state.playType === "play" ? headImg("perform1.png") : headImg("sing1.png")} />
-              <div>{state.playType === "play" ? "演奏场景" : "演唱场景"}</div>
-            </div>
-          )}
+          {
+            state.modeType === "practise" &&
+              <div class={[styles.modeWarn, "practiseModeWarn", state.platform === IPlatform.PC && state.musicScoreBtnDirection === "left" ? styles.modeWarnRight : ""]}>
+                <img src={state.playType === "play" ? headImg("perform1.png") : headImg("sing1.png")} />
+                <div>{state.playType === "play" ? "演奏场景" : "演唱场景"}</div>
+              </div>
+          }
           {/* 功能按钮 */}
           <div
             class={[styles.headRight]}
@@ -737,7 +735,7 @@ export default defineComponent({
               <img style={{ display: state.section.length === 2 ? "" : "none" }} class={styles.iconBtn} src={headImg(`section2.png`)} />
               <span>选段</span>
             </div>
-            {
+            {(
               <>
                 <div
                   style={{ display: metronomeBtn.value.display ? "" : "none" }}
@@ -761,7 +759,7 @@ export default defineComponent({
                   </Popup>
                 }
               </>
-            }
+            )}
             {/* {state.enableNotation ? (
               <Popover trigger="manual" v-model:show={headData.musicTypeShow} class={state.platform === IPlatform.PC && styles.pcTransPop} placement={state.platform === IPlatform.PC ? "top-end" : "bottom-end"} overlay={false} offset={state.platform === IPlatform.PC ? [0, 40] : [0, 8]}>
                 {{
@@ -785,7 +783,7 @@ export default defineComponent({
             ) : null} */}
             {state.musicRendered && !query.lessonTrainingId && !query.questionId && state.isConcert && (
               <div
-                class={[styles.btn, state.playState === "play" && fingeringBtn.value.disabled && styles.disabled, "driver-10"]}
+                class={[styles.btn, state.playState === "play" && fingeringBtn.value.disabled && styles.disabled]}
                 onClick={() => {
                   toggleMusicSheet.toggle(true);
                 }}
@@ -845,31 +843,14 @@ export default defineComponent({
         {isAllBtnsStudent.value && !query.isCbs && showGuideIndex.value && <StudentTop></StudentTop>} */}
 
         {/* 练习模式功能引导 加载音频完成 不是会员 */}
-        {state.modeType === "practise" && headTopData.modeType !== "init" && !query.isCbs && state.audioDone && !state.isVip && (
-          <PractiseDriver
-            statusAll={{
-              subjectStatus: state.musicRendered && !query.lessonTrainingId && !query.questionId && state.isConcert,
-              modelTypeStatus: toggleBtn.value.display
-            }}
-          />
-        )}
+        {state.modeType === "practise" && !query.isCbs && state.audioDone && !state.isVip && <PractiseDriver />}
         {/* 跟练模式功能引导 加载音频完成 不是会员 */}
-        {state.modeType === "follow" && headTopData.modeType !== "init" && !query.isCbs && state.audioDone && !state.isVip && (
-          <FollowDriver
-            statusAll={{
-              subjectStatus: state.musicRendered && !query.lessonTrainingId && !query.questionId && state.isConcert,
-            }}
-          />
-        )}
+        {state.modeType === "follow" && !query.isCbs && state.audioDone && !state.isVip && <FollowDriver />}
         {/* 评测模式功能引导 加载音频完成 不是会员 */}
-        {state.modeType === "evaluating" && headTopData.modeType !== "init" && !evaluatingData.earphoneMode && !query.isCbs && state.audioDone && !state.isVip && evaluatingData.websocketState && !evaluatingData.startBegin && evaluatingData.checkEnd && (
-          <EvaluatingDriver
-            statusAll={{
-              subjectStatus: state.musicRendered && !query.lessonTrainingId && !query.questionId && state.isConcert,
-            }}
-          />
-        )}
+        {state.modeType === "evaluating" && !evaluatingData.earphoneMode && !query.isCbs && state.audioDone && !state.isVip && <EvaluatingDriver />}
+        {/* 评测模式-结果弹窗 功能引导 加载音频完成 不是会员 */}
+        {state.modeType === "evaluating" && evaluatingData.resulstMode && !evaluatingData.earphoneMode && !query.isCbs && state.audioDone && !state.isVip && <EvaluatingResultDriver />}
       </>
     );
   },
-});
+});

+ 26 - 16
src/page-instrument/header-top/settting/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, reactive } from "vue";
+import { defineComponent, reactive, computed } from "vue";
 import styles from "./index.module.less"
 import { headImg } from "../image";
 import { headTopData } from "../index"
@@ -33,6 +33,12 @@ export default defineComponent({
 			state.setting.frequency = currentFrequency >= 0 ? currentFrequency : 0
 		}
         const formatterTimeMs = (value: any) => value = String(Math.min(3000, value));
+
+        const notationList = computed(() => {
+            const list = state.enableNotation ? [{name:'五线谱',value:'staff'},{name:'首调',value:'firstTone'},{name:'固定谱',value:'fixedTone'}] : [{name:'首调',value:'firstTone'},{name:'固定谱',value:'fixedTone'}];
+            return list;
+        });
+
 		return () => (
 			<div class={[styles.settting, styles[state.modeType]]}>
                 <div class={styles.head}>
@@ -158,21 +164,25 @@ export default defineComponent({
                                 </div>
                             </div> : null                        
                         }
-                        <div class={styles.cellBox}>
-                            <div class={styles.tit}>转谱</div>
-                            <div class={styles.radioBox}>
-                                {
-                                    [{name:'五线谱',value:'staff'},{name:'首调',value:'firstTone'},{name:'固定谱',value:'fixedTone'}].map(item=>{
-                                        return <div class={ state.musicRenderType===item.value && styles.active } onClick={ ()=>{ 
-                                            state.musicRenderType = item.value as any
-                                            // resetRenderMusicScore(state.musicRenderType)
-                                            headTopData.settingMode = false
-                                            refreshMusicSvg();
-                                        } }>{item.name}</div>
-                                    })
-                                }
-                            </div>
-                        </div>
+                        {
+                            state.enableNotation || state.specialShowNotation ? 
+                            <div class={styles.cellBox}>
+                                <div class={styles.tit}>转谱</div>
+                                <div class={styles.radioBox}>
+                                    {
+                                        notationList.value.map(item=>{
+                                            return <div class={ state.musicRenderType===item.value && styles.active } onClick={ ()=>{ 
+                                                state.musicRenderType = item.value as any
+                                                // resetRenderMusicScore(state.musicRenderType)
+                                                headTopData.settingMode = false
+                                                refreshMusicSvg();
+                                            } }>{item.name}</div>
+                                        })
+                                    }
+                                </div>
+                            </div> : null
+                        }
+
                         <div class={styles.cellBtnBox}>
                             <img  src={headImg("tpbz.png")} onClick={() => (helperData.screenModelShow = true)} />
                             <img  src={headImg("yjfk.png")} onClick={() => (helperData.recommendationShow = true)} />

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

@@ -182,6 +182,9 @@ export default defineComponent({
       // state.times = resetFrequency(state.times);
       state.times = setNoteHalfTone(state.times);
       console.log("🚀 ~ state.times:", state.times, state.subjectId, state);
+      nextTick(() => {
+        state.activeMeasureIndex = state.times[0].MeasureNumberXML;
+      })
       // 一行谱
       if (state.isSingleLine) {
         // 音符添加位置信息bbox
@@ -322,7 +325,7 @@ export default defineComponent({
               fingerBox: {
                 position: "absolute",
                 width: state.fingeringInfo.width,
-                height: "100%",
+                height: "80%",
                 right: state.playBtnDirection === "right" ? "initial" : 0,
                 left: state.playBtnDirection === "right" ? 0 : "initial",
                 top: 0,
@@ -336,7 +339,7 @@ export default defineComponent({
               fingerBox: {
                 position: "absolute",
                 width: state.fingeringInfo.width,
-                height: "100%",
+                height: "80%",
                 right: 0,
                 top: 0,
               },

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

@@ -77,7 +77,7 @@ export function initSmoothAnimation() {
    smoothAnimationState.aveSpeed = (canvasDomPath / (state.times[state.times.length - 1].time - state.times[0].time) / 1000) * 16.67
    // 当前屏幕的宽度
    calcClientWidth()
-   document.addEventListener("resize", calcClientWidth)
+   window.addEventListener("resize", calcClientWidth)
    // 初始化 只有练习模式 才显示
    state.modeType === "practise" && (smoothAnimationState.isShow.value = true)
    console.log(smoothAnimationState, "一行谱小鸟数据")
@@ -88,7 +88,7 @@ export function initSmoothAnimation() {
  */
 export function destroySmoothAnimation() {
    smoothAnimationState.isShow.value = false
-   document.removeEventListener("resize", calcClientWidth)
+   window.removeEventListener("resize", calcClientWidth)
    smoothAnimationState.smoothAnimationBoxDom?.remove()
    Object.assign(smoothAnimationState, {
       canvasDom: null,

+ 30 - 6
src/state.ts

@@ -19,6 +19,7 @@ import { moveSmoothAnimation, smoothAnimationState, moveSmoothAnimationByPlayTim
 import { storeData } from "/src/store";
 import { downloadXmlStr } from "./view/music-score"
 import { musicScoreRef } from "/src/page-instrument/view-detail/index"
+import { headTopData } from "/src/page-instrument/header-top/index";
 
 const query: any = getQuery();
 
@@ -28,7 +29,7 @@ export type IDifficulty = "BEGINNER" | "ADVANCED" | "PERFORMER";
 export enum EnumMusicRenderType {
   /** 五线谱 */
   staff = "staff",
-  /** 简谱 */
+  /** 简谱(首调) */
   firstTone = "firstTone",
   /** 固定音高 */
   fixedTone = "fixedTone",
@@ -277,6 +278,8 @@ const state = reactive({
   enableEvaluation: true,
   /** 是否支持转谱 */
   enableNotation: false,
+  /** 后台设置不能转谱,但是默认谱面不是五线谱时,需要显示转谱按钮,此时只能转首调和固定调 */
+  specialShowNotation: false,
   /** 曲谱ID */
   examSongId: "",
   /** 内容平台的曲谱ID,可能会和业务端的id不一样 */
@@ -1471,10 +1474,17 @@ const setState = (data: any, index: number) => {
     pitchTrack = data.musicalInstruments?.find((item: any) => item.code === musicalCode)
   }
   let musicalRenderType = ''
-  if (pitchTrack?.defaultScore) {
-    musicalRenderType = pitchTrack?.defaultScore === 'STAVE' ? 'staff' : pitchTrack?.defaultScore === 'JIAN' ? 'fixedTone' : pitchTrack?.defaultScore === 'FIRST' ? 'firstTone' : ''
-  }
-  state.musicRenderType = query.musicRenderType || musicalRenderType || EnumMusicRenderType.firstTone;
+  // if (pitchTrack?.defaultScore) {
+  //   musicalRenderType = pitchTrack?.defaultScore === 'STAVE' ? 'staff' : pitchTrack?.defaultScore === 'JIAN' ? 'fixedTone' : pitchTrack?.defaultScore === 'FIRST' ? 'firstTone' : ''
+  // }
+  // state.musicRenderType = query.musicRenderType || musicalRenderType || EnumMusicRenderType.firstTone;
+  /**
+   * 2024.7.30,使用新的字段
+   * 谱面类型,scoreType
+   * STAVE("五线谱"),JIAN("固定调"),FIRST("首调"),
+   */
+  musicalRenderType = data.scoreType === 'STAVE' ? 'staff' : data.scoreType === 'JIAN' ? 'fixedTone' : data.scoreType === 'FIRST' ? '' : 'firstTone';
+  state.musicRenderType = query.musicRenderType || musicalRenderType || EnumMusicRenderType.firstTone;  
   /**
    * TODO:摇篮曲特殊处理
    */
@@ -1483,7 +1493,15 @@ const setState = (data: any, index: number) => {
       state.musicRenderType = EnumMusicRenderType.firstTone;
     }
   }
-  state.enableNotation = pitchTrack ? data.isConvertibleScore && pitchTrack.transferFlag : data.isConvertibleScore
+  /**
+   * 2024.7.30,使用新的字段
+   * 能否转谱,isConvertibleScore
+   * true:能转谱,false:不能转谱
+   * 额外的逻辑:后台设置不能转谱时,如果默认谱面不是五线谱,需要显示转谱按钮,但是只能转首调和固定调
+   */  
+  // state.enableNotation = pitchTrack ? data.isConvertibleScore && pitchTrack.transferFlag : data.isConvertibleScore
+  state.enableNotation = data.isConvertibleScore
+  state.specialShowNotation = !data.isConvertibleScore && data.scoreType !== 'STAVE';
   console.log("state对象", state);
   // 评测基准频率
   state.baseFrequency = data.evaluationFrequency ? data.evaluationFrequency.split(",")[0] : 440
@@ -1772,6 +1790,7 @@ watch(
 /** 刷新谱面 */
 export const refreshMusicSvg = () => {
   resetBaseRate();
+  state.activeMeasureIndex = 0;
   state.loadingText = '正在加载中,请稍等…'
   // 销毁旋律线
   destroySmoothAnimation()
@@ -1787,5 +1806,10 @@ watch(
         smoothAnimationState.osdmScrollDomWith = smoothAnimationState.osdmScrollDom.offsetWidth | 0
       }
     })
+    // 如果有指法,并且是竖向指法时,切换指法时,谱面宽度变化,需要重新渲染谱面
+    if (state.fingeringInfo?.name && state.fingeringInfo.direction === "vertical" && !state.isSingleLine) {
+      headTopData.settingMode = false;
+      refreshMusicSvg();
+    }
   }
 )

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

@@ -128,7 +128,11 @@ const sendOffsetTime = async (offsetTime: number) => {
 export const handleStartEvaluat = async () => {
   if (state.modeType === "evaluating") {
     handleCancelEvaluat();
+    // 放下面会在异步之后执行 旋律线可能在会隐藏不了
+    state.modeType = "practise";
   } else {
+    // 放下面会在异步之后执行 旋律线可能在会隐藏不了
+    state.modeType = "evaluating";
     if (state.platform !== "PC") {
       // 评测前先检查APP端的websocket状态
       const res = await api_checkSocketStatus();
@@ -142,7 +146,7 @@ export const handleStartEvaluat = async () => {
       handleStopPlay();
     }
   }
-  state.modeType = state.modeType === "evaluating" ? "practise" : "evaluating";
+  //state.modeType = state.modeType === "evaluating" ? "practise" : "evaluating";
   if (state.modeType !== "evaluating") {
     // 切换到练习模式,卸载评测模块
     evaluatingData.rendered = false;