lex 1 yıl önce
ebeveyn
işleme
f40b29717e

+ 95 - 95
src/views/tempo-practice/beat-desc.ts

@@ -5,7 +5,7 @@ export const beatDesc = {
     attribute: [
       {
         number: 4, // 对应几分音符
-        type: 'sond' // 音符 | 休止符
+        type: 'sound' // 音符 | 休止符
       }
     ]
   },
@@ -25,11 +25,11 @@ export const beatDesc = {
     attribute: [
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -43,7 +43,7 @@ export const beatDesc = {
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -53,19 +53,19 @@ export const beatDesc = {
     attribute: [
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -75,15 +75,15 @@ export const beatDesc = {
     attribute: [
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -93,15 +93,15 @@ export const beatDesc = {
     attribute: [
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -112,11 +112,11 @@ export const beatDesc = {
       {
         number: 8,
         point: true, // 点
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -126,12 +126,12 @@ export const beatDesc = {
     attribute: [
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
         point: true,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -141,16 +141,16 @@ export const beatDesc = {
     liaison: true, // 是否连音
     attribute: [
       {
-        number: 16,
-        type: 'sond'
+        number: 8,
+        type: 'sound'
       },
       {
-        number: 16,
-        type: 'sond'
+        number: 8,
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -161,27 +161,27 @@ export const beatDesc = {
     attribute: [
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
-        number: 8,
-        type: 'sond'
+        number: 16,
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
-        number: 8,
-        type: 'sond'
+        number: 16,
+        type: 'sound'
       }
     ]
   },
@@ -191,15 +191,15 @@ export const beatDesc = {
     attribute: [
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -213,11 +213,11 @@ export const beatDesc = {
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -231,15 +231,15 @@ export const beatDesc = {
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -251,7 +251,7 @@ export const beatDesc = {
       {
         number: 4,
         point: true,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -272,11 +272,11 @@ export const beatDesc = {
     attribute: [
       {
         number: 4,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -286,15 +286,15 @@ export const beatDesc = {
     attribute: [
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 4,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -304,15 +304,15 @@ export const beatDesc = {
     attribute: [
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -326,11 +326,11 @@ export const beatDesc = {
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -340,19 +340,19 @@ export const beatDesc = {
     attribute: [
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -363,19 +363,19 @@ export const beatDesc = {
     attribute: [
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -385,19 +385,19 @@ export const beatDesc = {
     attribute: [
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -407,7 +407,7 @@ export const beatDesc = {
     attribute: [
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
@@ -415,11 +415,11 @@ export const beatDesc = {
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -429,23 +429,23 @@ export const beatDesc = {
     attribute: [
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -459,19 +459,19 @@ export const beatDesc = {
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -485,19 +485,19 @@ export const beatDesc = {
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -507,27 +507,27 @@ export const beatDesc = {
     attribute: [
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -539,15 +539,15 @@ export const beatDesc = {
       {
         number: 8,
         point: true,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -557,16 +557,16 @@ export const beatDesc = {
     attribute: [
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
         point: true,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   },
@@ -576,20 +576,20 @@ export const beatDesc = {
     attribute: [
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 8,
-        type: 'sond'
+        type: 'sound'
       },
       {
         number: 16,
-        type: 'sond'
+        type: 'sound'
       }
     ]
   }
-};
+} as any;

+ 157 - 0
src/views/tempo-practice/beat-tick.ts

@@ -0,0 +1,157 @@
+import { reactive } from 'vue';
+import tockAndTick from './tockAndTick.json';
+import { Howl } from 'howler';
+import { initSelectScorePart, setting } from './setting';
+import { beatDesc } from './beat-desc';
+
+const beatData = reactive({
+  list: [] as number[],
+  len: 0,
+  tickEnd: false,
+  /** 节拍器时间 */
+  beatLengthInMilliseconds: 0,
+  loopTime: 0, // 循环时长
+  state: '',
+  source1: '' as any,
+  source2: new Howl({
+    src: tockAndTick.tock
+  }) as any,
+  index: 0,
+  show: false
+});
+
+const handlePlay = (i: number, source: any, timer: any) => {
+  let payBeatTime = new Date().getTime();
+  return new Promise(resolve => {
+    if (beatData.tickEnd) {
+      resolve(i);
+      return;
+    }
+
+    let timeSppedEnum = 16.7;
+    const proofTime = () => {
+      if (setting.playState !== 'play') {
+        return;
+      }
+      setTimeout(() => {
+        const currentTime = new Date().getTime();
+        // 两次定时任务时间间隔
+        const diffTime = currentTime - payBeatTime;
+        if (diffTime >= beatData.loopTime) {
+          beatData.index++;
+          if (timer.type === 'sound') {
+            if (source) source.play();
+          }
+
+          resolve(i);
+          payBeatTime = currentTime;
+        } else {
+          if (Math.abs(diffTime - beatData.loopTime) <= timeSppedEnum) {
+            // 为了处理最后循环时间,用循环耗时
+            for (let index = 0; index < 500000; index++) {
+              let forTime = new Date().getTime();
+              if (Math.abs(forTime - payBeatTime) >= beatData.loopTime) {
+                beatData.index++;
+                if (timer.type === 'sound') {
+                  if (source) source.play();
+                }
+                resolve(i);
+                payBeatTime = forTime;
+                break;
+              }
+            }
+          } else {
+            proofTime();
+          }
+        }
+      }, timeSppedEnum);
+    };
+    proofTime();
+  });
+};
+
+/** 开始节拍器 */
+export const handleStartBeat = async () => {
+  beatData.show = true;
+  beatData.tickEnd = false;
+  beatData.index = 0;
+  beatData.beatLengthInMilliseconds = (60 / setting.speed) * 1000;
+  let startTime = +new Date();
+  for (let i = 0; i < setting.scorePart.length; i++) {
+    if (beatData.tickEnd) return false;
+    for (let j = 0; j < beatData.len; j++) {
+      if (beatData.tickEnd) return false;
+      // 提前结束, 直接放回false
+      const part = setting.scorePart[i][j];
+      const params = {
+        ...part,
+        ...beatDesc[part.index]
+      };
+      // console.log(params);
+      const single16th = beatData.beatLengthInMilliseconds;
+      const source = beatData.source2;
+      for (let g = 0; g < params.attribute.length; g++) {
+        let time = 0;
+        const attr = params.attribute[g];
+        // 计算每一拍需要的时长
+        // 四分音符 有延音
+        switch (attr.number) {
+          case 4:
+            if (attr.point) {
+              time = single16th * 0.5 * 3;
+            } else {
+              time = single16th;
+            }
+            break;
+          case 8:
+            // 连音
+            if (params.liaison) {
+              time = single16th * (1 / params.beatNum);
+            } else {
+              time = single16th * 0.5;
+            }
+            break;
+          case 16:
+            if (params.liaison) {
+              time = single16th * (1 / params.beatNum);
+            } else {
+              time = single16th * 0.25;
+            }
+            break;
+        }
+
+        await handlePlay(i, source, {
+          time,
+          type: attr.type
+        });
+        beatData.loopTime = time;
+        initSelectScorePart(i, j);
+      }
+    }
+  }
+
+  console.log(+new Date() - startTime);
+  beatData.show = false;
+  handleStartBeat();
+  return true;
+};
+
+/** 设置节拍器
+ * @param beatLengthInMilliseconds 节拍间隔时间
+ * @param beat 节拍数
+ */
+export const handleInitBeat = (
+  beatLengthInMilliseconds: number,
+  beat: number
+) => {
+  beatData.state = '';
+  beatData.beatLengthInMilliseconds = beatLengthInMilliseconds;
+  beatData.loopTime = beatLengthInMilliseconds;
+  beatData.len = beat;
+};
+
+/** 节拍器暂停 */
+export const hendleEndBeat = () => {
+  beatData.tickEnd = true;
+  initSelectScorePart();
+};

+ 54 - 18
src/views/tempo-practice/index.module.less

@@ -46,7 +46,7 @@
   justify-content: center;
   flex: 1 auto;
   flex-wrap: wrap;
-  gap: 22px 0;
+  gap: 15px 0;
   max-width: 900px;
   margin: 0 auto;
 }
@@ -57,12 +57,23 @@
   justify-content: center;
 
   &.small {
-    margin: 0 16px;
+    width: 50%;
+    // margin: 0 16px;
+
+    &:nth-child(2n + 1) {
+      justify-content: flex-end;
+      padding-right: 16px;
+    }
+
+    &:nth-child(2n + 2) {
+      justify-content: flex-start;
+      padding-left: 16px;
+    }
 
     .beat {
       border: 2px solid #fff;
-      width: 70px;
-      height: 94px;
+      width: 65px;
+      height: 86px;
       cursor: pointer;
 
       &::before,
@@ -77,23 +88,23 @@
     }
   }
 
-  &.minLength {
-    &:nth-child(2n + 1) {
-      margin-left: 10vw;
-    }
+  // &.minLength {
+  //   &:nth-child(2n + 1) {
+  //     margin-left: 10vw;
+  //   }
 
-    &:nth-child(2n + 2) {
-      margin-right: 10vw;
-    }
-  }
+  //   &:nth-child(2n + 2) {
+  //     margin-right: 10vw;
+  //   }
+  // }
 
-  &.maxLength {
-    margin: 0 12px;
+  // &.maxLength {
+  //   margin: 0 12px;
 
-    .beat {
-      margin: 0 7px;
-    }
-  }
+  //   .beat {
+  //     margin: 0 7px;
+  //   }
+  // }
 
   .beat {
     display: flex;
@@ -186,6 +197,12 @@
     align-items: center;
 
 
+    :global {
+      .van-popover__wrapper {
+        flex: 1;
+      }
+    }
+
     .speedNum {
       flex: 1;
       font-size: 16px;
@@ -216,4 +233,23 @@
 .settingPopup {
   background: transparent;
   overflow: visible;
+}
+
+.popupContainer {
+  margin-top: -10px !important;
+  --van-popover-action-height: 30px;
+  --van-popover-action-font-size: 14px;
+  --van-popover-radius: 12px;
+  --van-popover-action-width: 85px;
+
+  :global {
+    .van-popover__content {
+      max-height: 200px;
+      overflow-y: auto;
+    }
+
+    .van-popover__action {
+      padding: 0 9px;
+    }
+  }
 }

+ 98 - 23
src/views/tempo-practice/index.tsx

@@ -14,17 +14,36 @@ import iconAdd from './images/icon-add.png';
 import { getImage } from './images/music';
 import j1 from './images/music/j-1.png';
 // import j2 from './images/music/j-2.png';
-import { Popup } from 'vant';
+import { Popover, Popup } from 'vant';
 import SettingModal from './setting-modal';
 import { randomScoreElement, renderScore, setting } from './setting';
 import { handleStartTick, hendleEndTick } from './tick';
+import { handleStartBeat, hendleEndBeat } from './beat-tick';
 
 export default defineComponent({
   name: 'tempo-practice',
   setup() {
     const state = reactive({
       settingStatus: false,
-      playType: 'beat' as 'beat' | 'tempo'
+      speedList: [
+        { text: '40', value: 40, color: '#060606' },
+        { text: '50', value: 50, color: '#060606' },
+        { text: '60', value: 60, color: '#060606' },
+        { text: '70', value: 70, color: '#060606' },
+        { text: '80', value: 80, color: '#060606' },
+        { text: '90', value: 90, color: '#060606' },
+        { text: '100', value: 100, color: '#060606' },
+        { text: '110', value: 110, color: '#060606' },
+        { text: '120', value: 120, color: '#060606' },
+        { text: '130', value: 130, color: '#060606' },
+        { text: '140', value: 140, color: '#060606' },
+        { text: '150', value: 150, color: '#060606' },
+        { text: '160', value: 160, color: '#060606' },
+        { text: '170', value: 170, color: '#060606' },
+        { text: '180', value: 180, color: '#060606' },
+        { text: '190', value: 190, color: '#060606' },
+        { text: '200', value: 200, color: '#060606' }
+      ]
     });
     // 返回
     const goback = () => {
@@ -35,23 +54,38 @@ export default defineComponent({
     const handlePlay = async () => {
       if (setting.playState === 'pause') {
         setting.playState = 'play';
-        const a = await handleStartTick();
-        console.log(a, 'play');
+        if (setting.playType === 'beat') {
+          await handleStartTick();
+        } else {
+          await handleStartBeat();
+        }
       } else {
-        setting.playState = 'pause';
-        hendleEndTick();
+        handleStopPlay();
       }
     };
     /** 播放类型 */
     const handlePlayType = () => {
-      if (state.playType === 'beat') {
-        state.playType = 'tempo';
+      handleStopPlay();
+      if (setting.playType === 'beat') {
+        setting.playType = 'tempo';
       } else {
-        state.playType = 'beat';
+        setting.playType = 'beat';
+      }
+    };
+
+    const handleStopPlay = () => {
+      setting.playState = 'pause';
+      if (setting.playType === 'beat') {
+        hendleEndTick();
+      } else {
+        hendleEndBeat();
       }
     };
 
     onMounted(() => {
+      state.speedList.forEach((item: any) => {
+        if (item.value === setting.speed) item.color = '#1CACF1';
+      });
       renderScore();
     });
     return () => (
@@ -69,20 +103,18 @@ export default defineComponent({
         </div>
 
         <div class={styles.container}>
-          {setting.scorePart.map((item: any) => (
+          {setting.scorePart.map((item: any, i: number) => (
             <div
               class={[
                 styles.beatSection,
-                setting.scorePart.length >= 2 && styles.small,
-                ((item.length <= 2 && item.length > 4) ||
-                  (item.length == 2 && setting.scorePart.length == 4)) &&
-                  styles.minLength,
-                item.length <= 4 && item.length != 2 && styles.maxLength
+                // item.length !== 1 &&
+                setting.scorePart.length >= 2 &&
+                  item.length !== 1 &&
+                  styles.small
               ]}>
-              {item.map((child: any) => (
-                // , styles.active
+              {item.map((child: any, j: number) => (
                 <div
-                  class={[styles.beat]}
+                  class={[styles.beat, child.selected ? styles.active : '']}
                   onClick={() => {
                     const obj = randomScoreElement(child.index);
                     child.index = obj.index;
@@ -107,21 +139,64 @@ export default defineComponent({
           </div>
           {/* 播放类型 */}
           <div class={styles.playType} onClick={handlePlayType}>
-            {state.playType === 'beat' ? (
+            {setting.playType === 'beat' ? (
               <img src={beat} />
             ) : (
               <img src={tempo} />
             )}
           </div>
           {/* 随机生成 */}
-          <div class={styles.randomTempo} onClick={() => renderScore()}>
+          <div
+            class={styles.randomTempo}
+            onClick={() => {
+              renderScore();
+              handleStopPlay();
+            }}>
             <img src={randDom} />
           </div>
           {/* 速度 */}
           <div class={styles.speedChange}>
-            <img src={iconPlus} class={styles.speedPlus} />
-            <div class={styles.speedNum}>{setting.speed}</div>
-            <img src={iconAdd} class={styles.speedAdd} />
+            <img
+              src={iconPlus}
+              class={styles.speedPlus}
+              onClick={() => {
+                if (setting.speed <= 40) return;
+                setting.speed -= 1;
+                handleStopPlay();
+              }}
+            />
+            <Popover
+              placement="top"
+              class={styles.popupContainer}
+              actions={state.speedList}
+              onSelect={(val: any) => {
+                if (val.value === setting.speed) return;
+                state.speedList.forEach((item: any) => {
+                  if (item.value === val.value) {
+                    item.color = '#1CACF1';
+                    setting.speed = val.value;
+                  } else {
+                    item.color = '#060606';
+                  }
+                });
+                handleStopPlay();
+              }}>
+              {{
+                reference: () => (
+                  <div class={styles.speedNum}>{setting.speed}</div>
+                )
+              }}
+            </Popover>
+
+            <img
+              src={iconAdd}
+              class={styles.speedAdd}
+              onClick={() => {
+                if (setting.speed >= 200) return;
+                setting.speed += 1;
+                handleStopPlay();
+              }}
+            />
           </div>
         </div>
 

+ 1 - 0
src/views/tempo-practice/setting-modal/index.module.less

@@ -11,6 +11,7 @@
     left: 50%;
     top: -6px;
     margin-left: -70px;
+    z-index: 9;
     width: 140px;
     height: 34px;
     background: url('../images/icon-set-title.png') no-repeat center center / contain;

+ 12 - 0
src/views/tempo-practice/setting-modal/index.tsx

@@ -11,6 +11,8 @@ import {
   tempo8
 } from '../setting';
 import { getImage } from '../images/music';
+import { hendleEndTick } from '../tick';
+import { hendleEndBeat } from '../beat-tick';
 
 export default defineComponent({
   emits: ['close'],
@@ -41,12 +43,22 @@ export default defineComponent({
       }
     };
 
+    const handleStopPlay = () => {
+      setting.playState = 'pause';
+      if (setting.playType === 'beat') {
+        hendleEndTick();
+      } else {
+        hendleEndBeat();
+      }
+    };
+
     const onSubmit = () => {
       setting.element = state.element;
       setting.beat = state.beat;
       setting.barLine = state.barLine;
       setting.tempo = state.tempo;
 
+      handleStopPlay();
       renderScore();
       emit('close');
     };

+ 28 - 3
src/views/tempo-practice/setting.ts

@@ -1,5 +1,6 @@
 import { reactive } from 'vue';
 import { handleInitTick } from './tick';
+import { handleInitBeat } from './beat-tick';
 
 export const setting = reactive({
   element: 'jianpu' as 'jianpu' | 'staff', // 元素
@@ -8,6 +9,7 @@ export const setting = reactive({
   tempo: ['1', '2', '3'] as any[], // 节奏形筛选
   scorePart: [] as any, // 生成谱面
   playState: 'pause' as 'pause' | 'play',
+  playType: 'tempo' as 'beat' | 'tempo',
   speed: 60 // 默认速度
 });
 
@@ -81,11 +83,16 @@ export const randomScoreElement = (element?: string) => {
 /** 生成谱面 */
 export const renderScore = () => {
   const barLine = Number(setting.barLine);
-  const beat = setting.beat.split('-')[1];
+  const beatA = setting.beat.split('-').map(i => Number(i));
+  let beat = beatA[1];
+  if (beatA[0] === 8) {
+    beat = beat / 3;
+  }
+  console.log(beat, 'beat');
   const tempBeat: any = [];
   for (let i = 0; i < barLine; i++) {
     tempBeat[i] = [];
-    for (let j = 0; j < Number(beat); j++) {
+    for (let j = 0; j < beat; j++) {
       tempBeat[i][j] = {
         ...randomScoreElement()
       };
@@ -94,5 +101,23 @@ export const renderScore = () => {
   setting.scorePart = tempBeat;
 
   const beatLengthInMilliseconds = (60 / setting.speed) * 1000;
-  handleInitTick(beatLengthInMilliseconds, Number(beat) || 4);
+  if (setting.playType === 'beat') {
+    handleInitTick(beatLengthInMilliseconds, Number(beat) || 4);
+  } else {
+    handleInitBeat(beatLengthInMilliseconds, Number(beat) || 4);
+  }
+  initSelectScorePart();
+};
+
+/** 初始化选中状态 */
+export const initSelectScorePart = (i?: number, j?: number) => {
+  setting.scorePart.forEach((part: Array<any>) => {
+    part.forEach((item: any) => {
+      item.selected = false;
+    });
+  });
+
+  if (i !== undefined && j !== undefined && setting.scorePart[i][j]) {
+    setting.scorePart[i][j].selected = true;
+  }
 };

+ 64 - 43
src/views/tempo-practice/tick.ts

@@ -1,7 +1,7 @@
 import { reactive } from 'vue';
 import tockAndTick from './tockAndTick.json';
 import { Howl } from 'howler';
-import { setting } from './setting';
+import { initSelectScorePart, setting } from './setting';
 
 const tickData = reactive({
   list: [] as number[],
@@ -18,47 +18,55 @@ const tickData = reactive({
 
 let diffTime = 0; // 每一次节拍时间差
 const handlePlay = (i: number, source: any) => {
+  let payBeatTime = new Date().getTime();
   return new Promise(resolve => {
-    const startTime = +new Date();
-    setTimeout(() => {
-      diffTime = 0;
-      if (tickData.tickEnd) {
-        resolve(i);
+    if (tickData.tickEnd) {
+      resolve(i);
+      return;
+    }
+    let timeSppedEnum = 16.7;
+    const proofTime = () => {
+      if (setting.playState !== 'play') {
         return;
       }
-      tickData.index++;
-
-      if (source) source.play();
-      resolve(i);
-      const endTime = +new Date();
-      // diffTime = endTime - startTime - tickData.beatLengthInMilliseconds;
-      // console.log(endTime - startTime, diffTime);
-    }, tickData.beatLengthInMilliseconds - diffTime);
+      setTimeout(() => {
+        const currentTime = new Date().getTime();
+        // 两次定时任务时间间隔
+        const diffTime = currentTime - payBeatTime;
+        if (diffTime >= tickData.beatLengthInMilliseconds) {
+          tickData.index++;
+          if (source) source.play();
+          resolve(i);
+          payBeatTime = currentTime;
+        } else {
+          if (
+            Math.abs(diffTime - tickData.beatLengthInMilliseconds) <=
+            timeSppedEnum
+          ) {
+            // 为了处理最后循环时间,用循环耗时
+            for (let index = 0; index < 500000; index++) {
+              let forTime = new Date().getTime();
+              if (
+                Math.abs(forTime - payBeatTime) >=
+                tickData.beatLengthInMilliseconds
+              ) {
+                tickData.index++;
+                if (source) source.play();
+                resolve(i);
+                payBeatTime = forTime;
+                break;
+              }
+            }
+          } else {
+            proofTime();
+          }
+        }
+      }, timeSppedEnum);
+    };
+    proofTime();
   });
 };
 
-// let timeSppedEnum = 16.7;
-// let payBeatTime = new Date().getTime();
-// const proofTime = () => {
-//   if (setting.playState !== 'play') {
-//     return;
-//   }
-//   let startTime = Date.now();
-//   requestAnimationFrame(() => {
-//     const endTime = Date.now();
-//     // 渲染时间大于16.6,就会让页面卡顿, 如果渲染时间大与16.6就下一个渲染帧去计算
-//     if (endTime - startTime < 16.7) {
-//       handlePlaying();
-//       proofTime();
-//     } else {
-//       setTimeout(() => {
-//         handlePlaying();
-//         proofTime();
-//       }, 16.7);
-//     }
-//   });
-// };
-
 /** 设置节拍器
  * @param beatLengthInMilliseconds 节拍间隔时间
  * @param beat 节拍数
@@ -87,20 +95,33 @@ export const handleStartTick = async () => {
   }
   tickData.index = 0;
   tickData.beatLengthInMilliseconds = (60 / setting.speed) * 1000;
-  for (let i = 0; i <= tickData.len; i++) {
-    // 提前结束, 直接放回false
+  let startTime = +new Date();
+  // const allTick: any = [];
+  for (let i = 0; i < setting.scorePart.length; i++) {
     if (tickData.tickEnd) return false;
-    const source =
-      i === 0 ? tickData.source1 : i === tickData.len ? null : tickData.source2;
-
-    await handlePlay(i, source);
+    for (let j = 0; j < tickData.len; j++) {
+      // 提前结束, 直接放回false
+      if (tickData.tickEnd) return false;
+      const source =
+        j === 0
+          ? tickData.source1
+          : j === tickData.len
+          ? null
+          : tickData.source2;
+      // console.log(j, 'source');
+      await handlePlay(j, source);
+      initSelectScorePart(i, j);
+    }
   }
-  console.log(+new Date());
+
+  // console.log(+new Date() - startTime);
   tickData.show = false;
+  handleStartTick();
   return true;
 };
 
 /** 节拍器暂停 */
 export const hendleEndTick = () => {
   tickData.tickEnd = true;
+  initSelectScorePart();
 };