import { defineComponent, reactive, onMounted, computed } from "vue"; import tockAndTick from "/src/constant/tockAndTick.json"; import { Howl } from "howler"; import { Popup } from "vant"; import styles from "./index.module.less"; import state from "/src/state"; import { browser } from "/src/utils/index"; import tickWav from "/src/assets/tick.mp3"; import tockWav from "/src/assets/tock.mp3"; import { metronomeData } from "/src/helpers/metronome" const tickData = reactive({ len: 0, reduceLen: 0, tickEnd: false, /** 节拍器时间 */ beatLengthInMilliseconds: [] as number[], index: 0, show: false }); // 是否使用系统节拍器 const isUseSystemBeat = computed(()=>{ return (state.playType === "play"&& !state.isOpenMetronome)||(state.playType === "sing" && !state.isSingOpenMetronome) }) // 使用哪个节拍器个数 const useLen = computed(()=>{ return isUseSystemBeat.value ? tickData.reduceLen : tickData.len }) let _time: NodeJS.Timeout // 关闭节拍器 export function closeTick(){ if (tickData.show) { _time && clearTimeout(_time) tickData.tickEnd = true tickData.show = false } } const tickPlayCb = (i: any, resolve: any, source: any) => { if (tickData.tickEnd) { resolve(i) return }; // 第一个点,延迟100ms再消失 if (i === 0) { setTimeout(() => { tickData.index++; }, 100); } else { tickData.index++; } // 当系统节拍器才播放声音,跟练模式需要播放系统节拍器的声音,评测模式,如果没有伴奏,也需要播放系统节拍器的声音 if (source && (isUseSystemBeat.value || state.modeType === 'follow' || (state.modeType === 'evaluating' && !state.accompany)) ) { const beatVolume = state.setting.beatVolume / 100 source.volume = beatVolume; if (source.volume <= 0) { source.muted = true } else { source.muted = false } source.play(); } resolve(i); } const handlePlay = (i: number, source: any | null) => { return new Promise((resolve) => { if (i === 0 ) { tickPlayCb(i, resolve, source); } else { _time=setTimeout(() => { tickPlayCb(i, resolve, source); }, Math.abs(tickData.beatLengthInMilliseconds[i-1])*1000/state.basePlayRate); } }); }; // HTMLAudioElement 音频 const audioData = reactive({ tick: null as unknown as HTMLAudioElement, tock: null as unknown as HTMLAudioElement, }); const createAudio = (src: string): Promise => { return new Promise((resolve) => { const a = new Audio(src); a.load(); a.onloadedmetadata = () => { resolve(a); }; a.onerror = () => { resolve(null); }; }); }; /** 设置节拍器 */ export const handleInitTick = () => { const beatLen = metronomeData.firstBeatTypeArr.length * (state.repeatedBeats ? 2 : 1) const beatLengthInMilliseconds = metronomeData.firstBeatTypeArr.map(item=>{ return item*state.times[0].measureLength }) tickData.beatLengthInMilliseconds = [...beatLengthInMilliseconds,...(state.repeatedBeats ? beatLengthInMilliseconds : [])] tickData.len = beatLen; // // 节拍器的个数除以2 直到小于等于4为止 // while (beat > 4 && beat % 2 === 0) { // beat = beat / 2; // } tickData.reduceLen = beatLen }; /** 开始节拍器 */ // 评测和练习模式,根据是否播放系统节拍器和mp3节拍器来控制是否发声,跟练模式百分之播 export const handleStartTick = async () => { tickData.show = true; tickData.tickEnd = false; tickData.index = 0; for(let i = 0; i <= useLen.value; i++){ // 提前结束, 直接放回false if (tickData.tickEnd) return false; // Audio 标签播放音频 const source = tickData.beatLengthInMilliseconds[i] < 0 ? audioData.tick : i === useLen.value ? null : audioData.tock; await handlePlay(i, source) } tickData.show = false; return true }; export default defineComponent({ name: "metronome", setup() { const posObj = reactive({ top: "0px", left: "0px" }) function initPos() { const musicAndSelectionDom = document.querySelector("#musicAndSelection") const osmdSvgPage1Dom = musicAndSelectionDom?.querySelector("#osmdSvgPage1") const stafflineDom = osmdSvgPage1Dom?.querySelector(".staffline") const musicAndSelectionDomPos = musicAndSelectionDom?.getBoundingClientRect() const osmdSvgPage1DomPos = osmdSvgPage1Dom?.getBoundingClientRect() const stafflineDomPos = stafflineDom?.getBoundingClientRect() Object.assign(posObj,{ top: (osmdSvgPage1DomPos?.top||0)-(musicAndSelectionDomPos?.top||0)+13+"px", left: (stafflineDomPos?.left||0)-(osmdSvgPage1DomPos?.left||0)+"px" }) } onMounted(() => { initPos() Promise.all([createAudio(tickWav), createAudio(tockWav)]).then( ([tick, tock]) => { if (tick) { audioData.tick = tick; } if (tock) { audioData.tock = tock; } } ); }); return () => ( tickData.show &&
{ Array.from({ length: useLen.value }).map((item,index)=>{ return
}) }
); }, });