123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- 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<HTMLAudioElement | null> => {
- 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 = {
- 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)-18+"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 &&
- <div class={styles.dots} style={posObj}>
- {
- Array.from({ length: useLen.value }).map((item,index)=>{
- return <div class={[styles.dot,((useLen.value - tickData.index)<=index)&&styles.hide]}></div>
- })
- }
- </div>
- );
- },
- });
|