123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 |
- import { reactive, watch } from "vue";
- import { tickUrl as tick, tockUrl as tock } from "/src/constant/audios";
- import { browser } from "./utils";
- import state from "../pages/detail/state";
- type IOptions = {
- speed: number;
- };
- const ac = window.AudioContext || (window as any).webkitAudioContext || (window as any).mozAudioContext || (window as any).msAudioContext;
- const browserInfo = browser();
- let tipsTimer: any = null; // 光标提示定时器
- export const metronomeData = reactive({
- disable: true,
- lineShow: false,
- isClick: false,
- metro: null as unknown as Metronome,
- metroList: [] as number[],
- activeList: [] as number[],
- metroMeasure: [] as any[],
- activeIndex: null as unknown as number,
- activeMetro: {} as any,
- cursorMode: 2 as number, // 光标模式:1:音符指针;2:节拍指针;3:关闭指针
- cursorTips: '' as string, // 光标模式提示文字
- });
- watch(
- () => metronomeData.cursorMode,
- () => {
- const img: HTMLElement = document.querySelector("#cursorImg-0")!;
- if (img) {
- switch (metronomeData.cursorMode) {
- case 1:
- img.classList.remove("lineHide");
- img.style.opacity = 'inherit'
- metronomeData.cursorTips = '您已切换到指针跟随音符播放';
- img.style.opacity = 'inherit'
- break;
- case 2:
- img.classList.add("lineHide");
- img.style.opacity = 'inherit'
- metronomeData.cursorTips = '您已切换到指针跟随节拍播放';
- console.log('光标',img)
- break;
- case 3:
- img.style.opacity = '0'
- metronomeData.cursorTips = '您已关闭指针显示';
- console.log('隐藏光标')
- break;
- default:
- break;
- }
- hideCursorTip()
- }
- }
- );
- class Metronome {
- ctx = new ac();
- playType = "tick";
- source = null as any; // 创建音频源头
- source1 = null as any;
- source2 = null as any;
- constructor(option?: IOptions) {}
- init(times: any[]) {
- this.calculation(times);
- metronomeData.activeList = [];
- return new Promise(async (resolve) => {
- if (this.source1 && this.source2) return resolve(true);
- this.source1 = await this.loadAudio1();
- this.source2 = await this.loadAudio2();
- resolve(true);
- });
- }
- // 播放
- sound = (currentTime: number) => {
- // console.log("🚀 ~ currentTime", currentTime)
- // currentTime = setCurrentTime(currentTime);
- let index = -1;
- let activeMetro = -1;
- for (let i = 0; i < metronomeData.metroList.length; i++) {
- const item = metronomeData.metroList[i];
- if (currentTime >= item) {
- // console.log(currentTime , item)
- index = i;
- activeMetro = item;
- } else {
- break;
- }
- }
- if (index > -1 && metronomeData.activeIndex !== index) {
- metronomeData.activeIndex = index;
- // console.log("播放", metronomeData.activeIndex);
- metronomeData.activeMetro = this.getStep(activeMetro);
- // console.log("🚀 ~ 节拍metronomeData.activeMetro",metronomeData.activeMetro.measureNumberIndex, metronomeData.activeMetro.index, metronomeData.activeMetro)
- this.playAudio();
- metronomeData.isClick = false;
- return;
- }
- metronomeData.isClick = false;
- };
- // 播放
- playAudio = () => {
- this.source = this.ctx.createBufferSource();
- this.source.buffer = metronomeData.activeMetro?.index === 0 ? this.source1 : this.source2;
- const gainNode = this.ctx.createGain();
- gainNode.gain.value = metronomeData.disable ? 0 : 0.4;
- this.source.connect(gainNode);
- gainNode.connect(this.ctx.destination);
- this.source.start(0); //立即播放
- };
- // 切换
- selectPlay() {}
- loadAudio1 = async () => {
- const audioUrl = tick; // "/tick.wav";
- const res = await fetch(audioUrl);
- const arrayBuffer = await res.arrayBuffer(); // byte array字节数组
- // console.log("🚀 ~ arrayBuffer", arrayBuffer)
- const audioBuffer = await this.ctx.decodeAudioData(arrayBuffer, function (decodeData) {
- return decodeData;
- });
- return audioBuffer;
- };
- loadAudio2 = async () => {
- const audioUrl = tock; //"/tock.wav";
- const res = await fetch(audioUrl);
- const arrayBuffer = await res.arrayBuffer(); // byte array字节数组
- const audioBuffer = await this.ctx.decodeAudioData(arrayBuffer, function (decodeData) {
- return decodeData;
- });
- return audioBuffer;
- };
- getStep(time: number) {
- for (let i = 0; i < metronomeData.metroMeasure.length; i++) {
- const list = metronomeData.metroMeasure[i];
- const item = list.find((n: any) => n.time === time);
- if (item) {
- // console.log('index',item)
- return item;
- }
- }
- return {};
- }
- // 计算 所有的拍子的时间
- calculation(times: any[]) {
- console.log("🚀 ~ times", times);
- // 1.统计有多少小节
- const measures: any[] = [];
- let xmlNumber = -1;
- for (let i = 0; i < times.length; i++) {
- const note = times[i];
- const measureNumberXML = note?.noteElement?.sourceMeasure?.measureNumber + 1 || -1;
- // console.log("🚀 ~ note?.noteElement?.sourceMeasure", note?.noteElement?.sourceMeasure)
- // console.log("🚀 ~ measureNumberXML", measureNumberXML, note)
- // console.log("🚀 ~ measureNumberXML", note)
- const measureListIndex = note?.noteElement?.sourceMeasure?.measureListIndex;
- if (measureNumberXML > -1) {
- if (measureNumberXML != xmlNumber) {
- const m = {
- measureNumberXML: measureNumberXML,
- measureNumberIndex: measureListIndex,
- numerator: note?.noteElement?.sourceMeasure?.ActiveTimeSignature?.numerator || 0,
- start: note.measures[0].time,
- end: note.measures[note.measures.length - 1].endtime,
- time: note.measures[note.measures.length - 1].endtime - note.measures[0].time,
- stave_x: note?.noteElement?.sourceMeasure?.verticalMeasureList?.[0]?.stave?.x || 0,
- end_x: (note?.stave?.end_x || 0) || 0,
- stepList: [] as number[],
- svgs: [] as any[],
- };
- // 2.统计小节的拍数
- // 3.统计小节的时长, 开始时间,结束时间
- // console.log(measureNumberXML,note.measures, times.filter((n: any) => n?.noteElement?.sourceMeasure?.measureListIndex == measureListIndex))
- if ([121].includes(state.subjectId)) {
- const _measures = times.filter((n: any) => n?.noteElement?.sourceMeasure?.measureListIndex == measureListIndex);
- note.measures = _measures;
- m.start = note.measures[0].time;
- m.end = note.measures[note.measures.length - 1].endtime;
- m.time = note.measures[note.measures.length - 1].endtime - note.measures[0].time;
- try {
- const tickables = note.noteElement.sourceMeasure.verticalMeasureList.reduce((arr: any[], value: any) => {
- arr.push(...value.vfVoices["1"].tickables);
- return arr;
- }, []);
- const xList: any[] = [];
- m.svgs = tickables
- .map((n: any) => {
- const x = n.getBoundingBox().x;
- if (!xList.includes(x) && n.duration !== "w") {
- xList.push(x);
- n._start_x = x;
- return n;
- }
- })
- .filter(Boolean)
- .sort((a: any, b: any) => a._start_x - b._start_x);
- // console.log(measureNumberXML, m.svgs)
- } catch (error) {
- console.log(error);
- }
- m.stepList = calculateMutilpleMetroStep(note.measures, m);
- } else {
- m.stepList = calculateMetroStep(note.measures, m);
- }
- measures.push(m);
- xmlNumber = measureNumberXML;
- }
- }
- }
- console.log(measures, measures.length,6667);
- let metroList: number[] = [];
- const metroMeasure: any[] = [];
- // 4.按照拍数将时长平均分配
- try {
- for (let i = 0; i < measures.length; i++) {
- const measure = measures[i];
- const noteStep = measure.time / measure.numerator;
- // console.log("🚀 ~ measure.measureNumberXML",measure.measureNumberXML, noteStep)
- const WIDTH = [121].includes(state.subjectId) ? 95 : 100;
- const widthStep = WIDTH / (measure.numerator + 1);
- metroMeasure[i] = [] as number[];
- // console.log('stepList', [...measure.stepList], measure.measureNumberXML)
- for (let j = 0; j < measure.numerator; j++) {
- const time = noteStep * j + measure.start;
- metroList.push(time);
- let left = "";
- if (measure.stepList[j] === -1 || (measure.measureNumberXML === 1 && !measure.stepList[j])) {
- continue
- }
- if (measure.stepList[j]) {
- left = measure.stepList[j] + "px";
- } else {
- const preLeft = measure.stepList[j - 1];
- left = !preLeft ? `${widthStep}%` : preLeft.toString().indexOf("%") > -1 ? `${preLeft} + ${widthStep}%` : `${preLeft}px + ${widthStep}%`;
- measure.stepList[j] = left;
- }
- metroMeasure[i].push({
- index: j,
- time,
- // left: (measure.stepList[j] ? measure.stepList[j] + 'px' : (j + 1) * widthStep + '%'),
- left: left?.indexOf("%") > -1 ? `calc(${left})` : left,
- measureNumberXML: measure.measureNumberXML,
- });
- }
- }
- } catch (error) {
- console.log(error);
- }
- // console.log(metroList, metroMeasure);
- // 5.得到所有的节拍时间
- metronomeData.metroList = metroList;
- metronomeData.metroMeasure = metroMeasure;
- }
- }
- // 计算拍子的时值
- function calculateMetroStep(arr: any[], m: any): number[] {
- const measureLength = arr.reduce((total: number, item: any) => {
- total += item._noteLength;
- return total;
- }, 0);
- const clap = measureLength / m.numerator;
- if (arr.length === 1) {
- const wholeNote = arr[0].svgElelent
- if (wholeNote && !wholeNote.isRest()) {
- const measure_bbox = wholeNote?.attrs?.el?.parentElement?.parentElement?.getBoundingClientRect?.() || { x: 0, right: 0 };
- let bbox = wholeNote?.attrs?.el?.getBoundingClientRect?.() || { x: 0 };
- let stepWidth = Math.abs(measure_bbox.right - bbox.x) / m.numerator
- let stepList: number[] = [];
- for(let i = 0; i < m.numerator; i++){
- // 是第一个小节,并且不是全音符,是弱起
- if (m.measureNumberXML === 1 && wholeNote.duration !== "w") {
- stepList.push(i === 0 ? bbox.x - measure_bbox.x : -1)
- } else {
- stepList.push(bbox.x - measure_bbox.x + i * stepWidth)
- }
- }
- // console.log("🚀 ~ stepList:", stepList, m.measureNumberXML)
- return stepList;
- }
- try {
- // 开头是休止符
- if (m.measureNumberXML === 1 && wholeNote && wholeNote.isRest()) {
- const measure_bbox = wholeNote?.attrs?.el?.parentElement?.parentElement?.getBoundingClientRect?.() || { x: 0, right: 0 };
- let bbox = wholeNote?.attrs?.el?.getBoundingClientRect?.() || { x: 0 };
- let stepWidth = Math.abs(measure_bbox.right - bbox.x) / m.numerator
- let stepList: number[] = [];
- for(let i = -1; i < m.numerator - 1; i++){
- stepList.push(bbox.x - measure_bbox.x + i * stepWidth)
- }
- // console.log(wholeNote?.attrs?.el, m.measureNumberXML)
- // console.log("🚀 ~ stepList:", stepList, m.measureNumberXML)
- return stepList;
- }
- } catch (error) {
- console.log("🚀 ~ error:", error)
- }
-
- return [];
- }
- // console.log("🚀 ~ arr", [...arr],`小节总时值: ${measureLength}`, clap, m.measureNumberXML);
- let totalLength = 0;
- let notes: any[] = [];
- let stepList: number[] = [];
- for (let i = 0; i < arr.length; i++) {
- const item = arr[i];
- item.index = i;
- const noteLength = item._noteLength;
- totalLength += noteLength;
- // 大于一拍
- const exceedStep = totalLength / clap;
- // console.log(`note`, item?.svgElelent?.attrs?.el,notes.length,{noteLength, exceedStep,clap}, m.measureNumberXML)
- if (exceedStep >= 1) {
- totalLength -= clap;
- // 一拍
- const measure_bbox = item?.svgElelent?.attrs?.el?.parentElement?.parentElement?.getBoundingClientRect?.() || { x: 0 };
- if (notes.length > 0) {
- let bbox = notes[0]?.svgElelent?.attrs?.el?.querySelector('.vf-note')?.getBoundingClientRect?.() || { x: 0 };
- let x: any = bbox.x - measure_bbox.x;
- if ((notes[0]._noteLength / clap) >= 1) {
- const nextNote = arr[notes[0].index + 1]?.svgElelent?.attrs?.el?.getBoundingClientRect?.() || { x: measure_bbox.right } || { x: 0 };
- const stepWidth = Math.abs(bbox.x - nextNote.x) / 2;
- x = bbox.x - measure_bbox.x + stepWidth;
- // console.log(`音符超一拍`, notes[0]?.svgElelent?.attrs?.el, arr[notes[0].index + 1]?.svgElelent?.attrs?.el, bbox.x - nextNote.x, stepWidth, m.measureNumberXML);
- }
- // console.log(`一拍`, notes[0]?.svgElelent?.attrs?.el, m.measureNumberXML, notes[0]._noteLength , clap, 'aa')
- stepList.push(x);
- } else {
- let bbox = item?.svgElelent?.attrs?.el?.querySelector('.vf-note')?.getBoundingClientRect?.() || { x: 0 };
- let x: any = bbox.x - measure_bbox.x
- // console.log(`一拍`, item?.svgElelent?.attrs?.el, m.measureNumberXML)
- stepList.push(x);
- }
- notes = [];
- let bbox = item?.svgElelent?.attrs?.el?.querySelector('.vf-note')?.getBoundingClientRect?.() || { x: 0 };
- let x: any = bbox.x - measure_bbox.x;
- let stepWidth = 0;
- if (exceedStep > 1) {
- // 二拍以上
- const nextNote = arr[i + 1]?.svgElelent?.attrs?.el?.querySelector('.vf-note')?.getBoundingClientRect?.() || { x: measure_bbox.right } || { x: 0 };
- stepWidth = Math.abs(bbox.x - nextNote.x) / Math.ceil(exceedStep);
- // console.log("二拍以上 ~ nextNote:",bbox.x , nextNote.x,stepWidth, item?.svgElelent?.attrs?.el,arr[i + 1]?.svgElelent?.attrs?.el, exceedStep);
- }
- for (let j = 1; j < exceedStep; j++) {
- totalLength -= clap;
- // console.log(`超一拍`,item?.svgElelent?.attrs?.el, m.measureNumberXML)
- stepList.push(x + stepWidth * j);
- }
- }
- //有时值就将音符加入
- if (totalLength > Number.EPSILON && totalLength > 0) {
- notes.push(item);
- }
- }
- stepList = stepList.reduce((list: any[], n: number) => {
- if (list.includes(n)) {
- list.push(undefined as any);
- } else {
- list.push(n);
- }
- return list;
- }, []);
- // console.log("stepList", [...stepList], m.measureNumberXML);
- // for (let i in stepList) {
- // stepList[i] = stepList[i] / state.musicZoom
- // }
- // console.log('🚀 ~ stepList:',stepList)
- return stepList;
- }
- // 计算单声部多声轨的拍子的时值
- function calculateMutilpleMetroStep(arr: any[], m: any): number[] {
- // console.log("🚀 ~ m:", [...m.svgs])
- const step = m.time / m.numerator;
- const measure_bbox = arr[0]?.svgElelent?.attrs?.el?.parentElement?.parentElement?.getBoundingClientRect?.() || { x: 0 };
- if (arr.length === 1) {
- const staveNote = m.svgs[0];
- // 大于一拍
- let bbox = staveNote?.attrs?.el?.getBoundingClientRect?.() || { x: 0 };
- if (staveNote && !staveNote.isRest()) {
- return [bbox.x - measure_bbox.x];
- }
- return [];
- }
- // console.log("🚀 ~ arr", arr, step, m.measureNumberXML);
- let total = 0;
- let notes: any[] = [];
- let stepList: number[] = [];
- for (let i = 0; i < arr.length; i++) {
- const item = arr[i];
- item._index = i;
- const noteTime = item.endtime - item.time;
- total += noteTime;
- let svgEle = m.svgs[i]?.attrs?.el;
- // 大于一拍
- let bbox = svgEle?.getBoundingClientRect?.() || { x: 0 };
- // console.log(m.measureNumberXML, svgEle, i)
- if (noteTime > step) {
- total -= step;
- // console.log('超过一拍了', notes, m.measureNumberXML)
- let x = bbox.x - measure_bbox.x;
- if (notes.length > 0) {
- svgEle = m.svgs[notes[0]._index]?.attrs?.el;
- bbox = svgEle?.getBoundingClientRect?.() || { x: 0 };
- x = bbox.x - measure_bbox.x;
- }
- stepList.push(x);
- notes = [];
- } else {
- notes.push(item);
- }
- // console.log(notes)
- if (Math.abs(total - step) < 0.001) {
- let x = bbox.x - measure_bbox.x;
- if (notes.length > 0) {
- svgEle = m.svgs[notes[0]._index]?.attrs?.el;
- bbox = svgEle?.getBoundingClientRect?.() || { x: 0 };
- x = bbox.x - measure_bbox.x;
- }
- // console.log("一拍",svgEle,notes,m.svgs, m.measureNumberXML);
- stepList.push(x);
- total = 0;
- notes = [];
- }
- }
- stepList = stepList.reduce((list: any[], n: number) => {
- if (list.includes(n)) {
- list.push(undefined as any);
- } else {
- list.push(n);
- }
- return list;
- }, []); //Array.from(new Set(stepList))
- // console.log('stepList', stepList, m.measureNumberXML)
- // for (let i in stepList) {
- // stepList[i] = stepList[i] / state.musicZoom
- // }
- // console.log('🚀 ~ stepList:',stepList)
- return stepList;
- }
- // 延迟兼容处理
- function setCurrentTime(time: number) {
- if (browserInfo.huawei || browserInfo.xiaomi) {
- time += 0.125;
- } else if (browserInfo.android) {
- time += 0.11;
- } else if (browserInfo.ios) {
- time += 0.01;
- }
- return time;
- }
- // 自动隐藏光标提示
- function hideCursorTip() {
- if (!tipsTimer) {
- tipsTimer = setTimeout(() => {
- metronomeData.cursorTips = ''
- clearTimeout(tipsTimer)
- tipsTimer = null
- }, 2000);
- } else {
- clearTimeout(tipsTimer)
- tipsTimer = setTimeout(() => {
- metronomeData.cursorTips = ''
- clearTimeout(tipsTimer)
- tipsTimer = null
- }, 2000);
- }
- }
- export default Metronome;
|