index.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import { defineComponent, reactive, onMounted, computed } from "vue";
  2. import tockAndTick from "/src/constant/tockAndTick.json";
  3. import { Howl } from "howler";
  4. import { Popup } from "vant";
  5. import styles from "./index.module.less";
  6. import state from "/src/state";
  7. import { browser } from "/src/utils/index";
  8. import tickWav from "/src/assets/tick.mp3";
  9. import tockWav from "/src/assets/tock.mp3";
  10. const tickData = reactive({
  11. len: 0,
  12. reduceLen: 0,
  13. tickEnd: false,
  14. /** 节拍器时间 */
  15. beatLengthInMilliseconds: 0,
  16. index: 0,
  17. show: false
  18. });
  19. // 是否使用系统节拍器
  20. const isUseSystemBeat = computed(()=>{
  21. return (state.playType === "play"&& !state.isOpenMetronome)||(state.playType === "sing" && !state.isSingOpenMetronome)
  22. })
  23. // 使用哪个节拍器个数
  24. const useLen = computed(()=>{
  25. return isUseSystemBeat.value ? tickData.reduceLen : tickData.len
  26. })
  27. let _time: NodeJS.Timeout
  28. // 关闭节拍器
  29. export function closeTick(){
  30. if (tickData.show) {
  31. _time && clearTimeout(_time)
  32. tickData.tickEnd = true
  33. tickData.show = false
  34. }
  35. }
  36. const handlePlay = (i: number, source: any | null) => {
  37. return new Promise((resolve) => {
  38. _time=setTimeout(() => {
  39. if (tickData.tickEnd) {
  40. resolve(i)
  41. return
  42. };
  43. tickData.index++;
  44. // 当系统节拍器才播放声音
  45. if (source && isUseSystemBeat.value) {
  46. const beatVolume = state.setting.beatVolume / 100
  47. source.volume = beatVolume;
  48. if (source.volume <= 0) {
  49. source.muted = true
  50. } else {
  51. source.muted = false
  52. }
  53. source.play();
  54. }
  55. resolve(i);
  56. }, tickData.beatLengthInMilliseconds);
  57. });
  58. };
  59. // HTMLAudioElement 音频
  60. const audioData = reactive({
  61. tick: null as unknown as HTMLAudioElement,
  62. tock: null as unknown as HTMLAudioElement,
  63. });
  64. const createAudio = (src: string): Promise<HTMLAudioElement | null> => {
  65. return new Promise((resolve) => {
  66. const a = new Audio(src);
  67. a.load();
  68. a.onloadedmetadata = () => {
  69. resolve(a);
  70. };
  71. a.onerror = () => {
  72. resolve(null);
  73. };
  74. });
  75. };
  76. /** 设置节拍器
  77. * @param beat 节拍数
  78. */
  79. export const handleInitTick = (beat: number) => {
  80. tickData.len = beat;
  81. // 节拍器的个数除以2 直到小于等于4为止
  82. while (beat > 4 && beat % 2 === 0) {
  83. beat = beat / 2;
  84. }
  85. tickData.reduceLen = beat
  86. };
  87. /** 开始节拍器 */
  88. // 评测和练习模式,根据是否播放系统节拍器和mp3节拍器来控制是否发声,跟练模式百分之播
  89. export const handleStartTick = async () => {
  90. tickData.show = true;
  91. tickData.tickEnd = false;
  92. tickData.index = 0;
  93. tickData.beatLengthInMilliseconds = (60 / state.speed) * 1000;
  94. for(let i = 0; i <= useLen.value; i++){
  95. // 提前结束, 直接放回false
  96. if (tickData.tickEnd) return false;
  97. // Audio 标签播放音频
  98. const source = i === 0 ? audioData.tick : i === useLen.value ? null : audioData.tock;
  99. await handlePlay(i, source)
  100. }
  101. tickData.show = false;
  102. return true
  103. };
  104. export default defineComponent({
  105. name: "metronome",
  106. setup() {
  107. const posObj = {
  108. top: "0px",
  109. left: "0px"
  110. }
  111. initPos()
  112. function initPos() {
  113. const musicAndSelectionDom = document.querySelector("#musicAndSelection")
  114. const osmdSvgPage1Dom = musicAndSelectionDom?.querySelector("#osmdSvgPage1")
  115. const stafflineDom = osmdSvgPage1Dom?.querySelector(".staffline")
  116. const musicAndSelectionDomPos = musicAndSelectionDom?.getBoundingClientRect()
  117. const osmdSvgPage1DomPos = osmdSvgPage1Dom?.getBoundingClientRect()
  118. const stafflineDomPos = stafflineDom?.getBoundingClientRect()
  119. Object.assign(posObj,{
  120. top: (osmdSvgPage1DomPos?.top||0)-(musicAndSelectionDomPos?.top||0)-18+"px",
  121. left: (stafflineDomPos?.left||0)-(osmdSvgPage1DomPos?.left||0)+"px"
  122. })
  123. }
  124. onMounted(() => {
  125. Promise.all([createAudio(tickWav), createAudio(tockWav)]).then(
  126. ([tick, tock]) => {
  127. if (tick) {
  128. audioData.tick = tick;
  129. }
  130. if (tock) {
  131. audioData.tock = tock;
  132. }
  133. }
  134. );
  135. });
  136. return () => (
  137. tickData.show &&
  138. <div class={styles.dots} style={posObj}>
  139. {
  140. Array.from({ length: useLen.value }).map((item,index)=>{
  141. return <div class={[styles.dot,((useLen.value - tickData.index)<=index)&&styles.hide]}></div>
  142. })
  143. }
  144. </div>
  145. );
  146. },
  147. });