Browse Source

选段+预备拍+节拍器+高级模式

liushengqiang 1 year ago
parent
commit
17ad1d8bed

+ 24 - 0
package-lock.json

@@ -11,6 +11,7 @@
         "clean-deep": "^3.4.0",
         "consola": "^2.15.3",
         "dayjs": "^1.11.7",
+        "howler": "^2.2.3",
         "lodash": "^4.17.21",
         "query-string": "^8.1.0",
         "store": "^2.0.12",
@@ -20,6 +21,7 @@
         "vue-router": "^4.1.6"
       },
       "devDependencies": {
+        "@types/howler": "^2.2.7",
         "@types/lodash": "^4.14.192",
         "@types/node": "^18.15.11",
         "@types/store": "^2.0.2",
@@ -2186,6 +2188,12 @@
       "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==",
       "dev": true
     },
+    "node_modules/@types/howler": {
+      "version": "2.2.7",
+      "resolved": "https://registry.npmjs.org/@types/howler/-/howler-2.2.7.tgz",
+      "integrity": "sha512-PEZldwZqJJw1PWRTpupyC7ajVTZA8aHd8nB/Y0n6zRZi5u8ktYDntsHj13ltEiBRqWwF06pASxBEvCTxniG8eA==",
+      "dev": true
+    },
     "node_modules/@types/lodash": {
       "version": "4.14.192",
       "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.192.tgz",
@@ -3169,6 +3177,11 @@
         "tslib": "^2.0.3"
       }
     },
+    "node_modules/howler": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/howler/-/howler-2.2.3.tgz",
+      "integrity": "sha512-QM0FFkw0LRX1PR8pNzJVAY25JhIWvbKMBFM4gqk+QdV+kPXOhleWGCB6AiAF/goGjIHK2e/nIElplvjQwhr0jg=="
+    },
     "node_modules/html-tags": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz",
@@ -5865,6 +5878,12 @@
       "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==",
       "dev": true
     },
+    "@types/howler": {
+      "version": "2.2.7",
+      "resolved": "https://registry.npmjs.org/@types/howler/-/howler-2.2.7.tgz",
+      "integrity": "sha512-PEZldwZqJJw1PWRTpupyC7ajVTZA8aHd8nB/Y0n6zRZi5u8ktYDntsHj13ltEiBRqWwF06pASxBEvCTxniG8eA==",
+      "dev": true
+    },
     "@types/lodash": {
       "version": "4.14.192",
       "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.192.tgz",
@@ -6648,6 +6667,11 @@
         "tslib": "^2.0.3"
       }
     },
+    "howler": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/howler/-/howler-2.2.3.tgz",
+      "integrity": "sha512-QM0FFkw0LRX1PR8pNzJVAY25JhIWvbKMBFM4gqk+QdV+kPXOhleWGCB6AiAF/goGjIHK2e/nIElplvjQwhr0jg=="
+    },
     "html-tags": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz",

+ 2 - 0
package.json

@@ -12,6 +12,7 @@
     "clean-deep": "^3.4.0",
     "consola": "^2.15.3",
     "dayjs": "^1.11.7",
+    "howler": "^2.2.3",
     "lodash": "^4.17.21",
     "query-string": "^8.1.0",
     "store": "^2.0.12",
@@ -21,6 +22,7 @@
     "vue-router": "^4.1.6"
   },
   "devDependencies": {
+    "@types/howler": "^2.2.7",
     "@types/lodash": "^4.14.192",
     "@types/node": "^18.15.11",
     "@types/store": "^2.0.2",

File diff suppressed because it is too large
+ 1 - 0
src/constant/tockAndTick.json


+ 10 - 393
src/helpers/formateMusic.ts

@@ -1,11 +1,9 @@
 import dayjs from "dayjs";
 import duration from "dayjs/plugin/duration";
 import state from "../state";
-// import appState from "/src/state";
 import { browser } from "../utils/index";
 import { isSpecialMark, isSpeedKeyword, Fraction, SourceMeasure, isGradientWords, GRADIENT_SPEED_RESET_TAG, StringUtil, OpenSheetMusicDisplay } from "/osmd-extended/src";
 import { GradualChange, speedInfo } from "./calcSpeed";
-import { useRoute } from "vue-router";
 const browserInfo = browser();
 dayjs.extend(duration);
 
@@ -27,9 +25,6 @@ export const getFixTime = (speed: number) => {
 	return state.isOpenMetronome ? (60 / speed) * formatBeatUnit(beatUnit) * (numerator / denominator) : 0;
 };
 
-const getLinkId = (): string => {
-	return location.hash.split("?")[0].split("/").pop() || "";
-};
 
 export const retain = (time: number) => {
 	return Math.ceil(time * 1000000) / 1000000;
@@ -56,19 +51,6 @@ export const getMeasureDurationDiff = (measure: any) => {
 	return SRealValue - RRealValue;
 };
 
-// const speedInfo: {[key in string]: number} = {
-//   'rall.': 1.333333333,
-//   'poco rit.': 1.333333333,
-//   'rit.': 1.333333333,
-//   'molto rit.': 1.333333333,
-//   'molto rall': 1.333333333,
-//   'lentando': 1.333333333,
-//   'allargando': 1.333333333,
-//   'morendo': 1.333333333,
-//   'accel.': .8,
-//   'calando': 2,
-//   'poco accel.': .8,
-// }
 
 /** 按照dorico的渐快渐慢生成对应的速度 */
 export const createSpeedInfo = (gradualChange: GradualChange | undefined, speed: number) => {
@@ -99,91 +81,6 @@ const tranTime = (str: string = "") => {
 	return `1970-01-01 00:${result}0`;
 };
 
-export const getAllNoteElements = (osmd: any) => {
-	const list: any[] = [];
-	const listById: {
-		[key: string]: any;
-	} = {};
-
-	for (const measure of osmd.drawer.graphicalMusicSheet.measureList) {
-		const activeMeasure = measure[0];
-		for (const tickable of activeMeasure.vfVoices["1"].tickables) {
-			list.push(tickable);
-			listById[tickable.attrs.id] = tickable;
-		}
-	}
-
-	return {
-		list,
-		listById,
-	};
-};
-
-export const setStepIndex = (osmd: any, num: number, prev?: number) => {
-	if (num || num === 0) {
-		// console.log(prev, num)
-		if (prev && num - prev === 1) {
-			osmd.cursor.next();
-		} else if (prev && num - prev > 0) {
-			while (num - prev > 0) {
-				prev++;
-				num - prev > 0;
-				osmd.cursor.next();
-			}
-		} else {
-			let i = 0;
-			osmd.cursor.reset();
-			while (i < num) {
-				i++;
-				if (osmd.cursor.hidden !== false) {
-					osmd.cursor.show();
-				} else {
-					osmd.cursor.next();
-				}
-			}
-		}
-	}
-};
-
-export const getIndex = (times: any[], currentTime: Number) => {
-	// console.log(currentTime)
-	if (currentTime > state.times[state.times.length - 1].endtime) {
-		return state.times.length - 1;
-	}
-	let index = 0;
-	for (let i = 0; i < times.length; i++) {
-		const item = times[i];
-		const prevItem = times[i - 1];
-		if (currentTime >= item.time) {
-			if (!prevItem || item.time != prevItem.time) {
-				index = item.i;
-			}
-		} else {
-			break;
-		}
-	}
-	if (state.sectionStatus && state.section.length === 2) {
-		// 限制不超过此范围
-		const startSection = state.befireSection || state.section[0];
-		index = Math.min(Math.max(index, startSection.i), state.section[1].i);
-		// console.log('endIndex', index)
-	}
-	return index;
-};
-
-/**
- * 获取连音线的音符
- * @param note
- */
-export const getTone = (note: any) => {
-	for (const slur of note.noteElement.slurs) {
-		if (slur.startNote.halfTone === slur.endNote.halfTone) {
-			return slur.startNote === note.noteElement ? slur.endNote : slur.startNote;
-		}
-	}
-	return null;
-};
-
 export const getSlursNote = (note: any, pos?: "start" | "end") => {
 	return pos === "end" ? note.noteElement.slurs[0]?.endNote : note.noteElement.slurs[0]?.startNote;
 };
@@ -192,24 +89,6 @@ export const getNoteBySlursStart = (note: any, anyNoteHasSlurs?: boolean, pos?:
 	let activeNote = note;
 	let slursNote = getSlursNote(activeNote, pos);
 
-	// if (!slursNote && anyNoteHasSlurs) {
-	//   for (const item of activeNote.measures) {
-	//     if (item.noteElement.slurs.length) {
-	//       slursNote = getSlursNote(item, pos)
-	//       activeNote = item
-	//     }
-	//   }
-	// }
-	// if (activeNote && slursNote !== activeNote.noteElement) {
-	//   // time = activeNote.time
-
-	//   for (const note of state.times) {
-	//     if (slursNote === note.noteElement) {
-	//       return note
-	//     }
-	//   }
-	// }
-
 	for (const item of activeNote.measures) {
 		if (item.noteElement.slurs.length) {
 			slursNote = getSlursNote(item, pos);
@@ -234,106 +113,6 @@ export const getParentNote = (note: any) => {
 	return parentNote;
 };
 
-/** 获取小节之间的连音线,仅同音高*/
-export const getNoteByMeasuresSlursStart = (note: any) => {
-	let activeNote = note;
-	let tieNote;
-	// console.log(note.noteElement)
-	if (note.noteElement.tie && note.noteElement.tie.StartNote) {
-		tieNote = note.noteElement.tie.StartNote;
-		// for (const s of Array.from(note.noteElement.tie) as any[]) {
-		//   const SMeasure = s.startNote.sourceMeasure
-		//   const EMeasure = s.endNote.sourceMeasure
-		//   const isNotSelf = SMeasure.MeasureNumberXML !== EMeasure.MeasureNumberXML
-		//   const isHomophony = s.startNote.noteElement.halfTone === s.endNote.noteElement.halfTone
-		//   const isNotEndNote = s.endNote !== note.noteElement
-		//   if (s && isNotSelf && isNotEndNote && isHomophony) {
-		//     tieNote = s.startNote
-		//     break
-		//   }
-		// }
-	}
-	if (activeNote && tieNote && tieNote !== activeNote.noteElement) {
-		// time = activeNote.time
-		for (const note of state.times) {
-			if (tieNote === note.noteElement) {
-				console.log(note);
-				return note;
-			}
-		}
-	}
-	return activeNote;
-};
-
-export const getActtiveNoteByTimes = (evt: MouseEvent) => {
-	const el = (evt.target as HTMLDivElement)?.dataset;
-	// console.log(state)
-
-	const data: any = {};
-	for (const time of state.times) {
-		if (time.id && !data[time.id]) {
-			data[time.id] = time;
-		}
-	}
-	const activeNote = data[el.id || ""];
-	// state.timesById = data
-	return activeNote;
-};
-
-const getPrevHasSourceNote = (note: any) => {
-	const indexOf = Math.max(state.times.indexOf(note) - 1, 0);
-
-	for (let index = indexOf; index >= 0; index--) {
-		const item = state.times[index];
-		if (item?.stave) {
-			return item;
-		}
-	}
-};
-
-export const getBoundingBoxByverticalNote = (note: any) => {
-	let measures = note?.noteElement?.sourceMeasure?.verticalMeasureList;
-	measures = !measures || !measures[0] ? note?.noteElement?.isRestFlag && getPrevHasSourceNote(note)?.noteElement?.sourceMeasure?.verticalMeasureList : measures;
-	let height = 0;
-	if (measures) {
-		const firstMeasure = measures[state.partIndex];
-		for (let index = 0; index < measures.length; index++) {
-			const measure = measures[index];
-			if (measure?.stave) {
-				const { height: measureHeight } = measure?.stave;
-				if (index > 0) {
-					height += measures[index - 1]?.stave.height;
-				}
-				height += measureHeight;
-				const { x, y, width, context, start_x, end_x } = firstMeasure?.stave;
-				return {
-					measureIndex: note?.noteElement?.sourceMeasure.measureListIndex || 0,
-					MeasureNumberXML: note?.noteElement?.sourceMeasure.MeasureNumberXML || 1,
-					start_x,
-					end_x,
-					height,
-					x,
-					y,
-					width,
-					context,
-					numerator: firstMeasure?.parentSourceMeasure?.ActiveTimeSignature?.numerator,
-				};
-			}
-		}
-	}
-	return {
-		measureIndex: 0,
-		height,
-		start_x: 0,
-		end_x: 0,
-		x: 0,
-		y: 0,
-		width: 0,
-		context: {
-			element: null,
-		},
-	};
-};
 
 export type FractionDefault = {
 	numerator: number;
@@ -445,66 +224,6 @@ export function getTimeByBeatUnit(beatUnit: string, bpm: number, denominator: nu
 	return (denominator / formatBeatUnit(beatUnit)) * bpm;
 }
 
-/** 获取第一个小节的节拍速度,替换初始的速度 */
-export const getFirstBpmByMeasures = (measures: SourceMeasure[]) => {
-	for (const item of measures) {
-		if (item.TempoInBPM) {
-			return getMeasureRealBpm(item);
-		}
-	}
-	return 90;
-};
-
-/**
- * 根据小节获取当前节拍下真实的速度
- * 如: 6/8拍 4分音符bpm速度为120 则计算出8分音符的速度为120 * 2
- */
-export const getMeasureRealBpm = (measure: SourceMeasure) => {
-	let bpm = measure.TempoInBPM;
-	let tempoExpression = null;
-	let activeTimeSignature: Fraction | null = null;
-	for (const expression of measure?.TempoExpressions) {
-		if (expression?.InstantaneousTempo.beatUnit) {
-			// 取最后一个有效的tempo
-			tempoExpression = expression;
-			activeTimeSignature = measure.ActiveTimeSignature;
-		}
-	}
-	if (tempoExpression && activeTimeSignature) {
-		bpm = tempoExpression?.InstantaneousTempo.TempoInBpm;
-		// let multiple = 1
-		// console.log(tempoExpression.InstantaneousTempo.beatUnit)
-
-		bpm = getTimeByBeatUnit(tempoExpression.InstantaneousTempo.beatUnit, bpm, activeTimeSignature.Denominator);
-	}
-	return bpm;
-};
-
-export const getEnvHostname = () => {
-	if (location.origin.indexOf("online") > -1) {
-		return "https://mstuonline.dayaedu.com";
-	} else if (location.origin.indexOf("dev") > -1) {
-		return "http://mstudev.dayaedu.com";
-	}
-	return "https://mstutest.dayaedu.com";
-};
-
-export const getTvIconUrl = () => {
-	if (location.origin.indexOf("online") > -1) {
-		return "https://mteaonline.dayaedu.com/#/guide";
-	} else if (location.origin.indexOf("dev") > -1) {
-		return "http://mteadev.dayaedu.com/#/guide";
-	}
-	return "https://mteatest.dayaedu.com/#/guide";
-};
-
-export const setPrefix = (url: string): string => {
-	if (url) {
-		return "?" + url;
-	}
-	return "";
-};
-
 export type CustomInfo = {
 	showSpeed: boolean;
 	parsedXML: string;
@@ -608,7 +327,7 @@ export const isRepeatWord = (text: string): boolean => {
 
 export const onlyVisible = (xml: string, partIndex: number): string => {
 	if (!xml) return "";
-	const detailId = getLinkId();
+	const detailId = state.examSongId;
 	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
 	const partList = xmlParse.getElementsByTagName("part-list")?.[0]?.getElementsByTagName("score-part") || [];
 	const partListNames = Array.from(partList).map((item) => item.getElementsByTagName("part-name")?.[0].textContent || "");
@@ -657,10 +376,6 @@ export const onlyVisible = (xml: string, partIndex: number): string => {
 						const index = firstMeasures.indexOf(parentMeasure);
 						const activeMeasure = part.getElementsByTagName("measure")[index];
 						setElementNoteBefore(metronomeContainer, parentMeasure, activeMeasure);
-						//   console.log(measureMetronomes, metronomesIndex, activeMeasure?.childNodes, activeMeasure?.childNodes[metronomesIndex])
-						//   activeMeasure?.insertBefore(metronomeContainer.cloneNode(true), activeMeasure?.childNodes[metronomesIndex])
-						//   // part.getElementsByTagName('measure')[index]?.appendChild(metronomeContainer.cloneNode(true))
-						//   // console.log(index, parentMeasure, firstMeasures.indexOf(parentMeasure))
 					}
 				});
 				/** word比较特殊需要精确到note位置 */
@@ -755,29 +470,6 @@ export const onlyVisible = (xml: string, partIndex: number): string => {
 				part.parentNode?.removeChild(part);
 			}
 		});
-		// 处理装饰音问题
-		const notes = xmlParse.getElementsByTagName("note");
-		const getNextvNoteDuration = (i: number) => {
-			let nextNote = notes[i + 1];
-			// 可能存在多个装饰音问题,取下一个非装饰音时值
-			for (let index = i; index < notes.length; index++) {
-				const note = notes[index];
-				if (!note.getElementsByTagName("grace")?.length) {
-					nextNote = note;
-					break;
-				}
-			}
-			const nextNoteDuration = nextNote?.getElementsByTagName("duration")[0];
-			return nextNoteDuration;
-		};
-		Array.from(notes).forEach((note, i) => {
-			const graces = note.getElementsByTagName("grace");
-			if (graces && graces.length) {
-				// if (i !== 0) {
-				// note.appendChild(getNextvNoteDuration(i)?.cloneNode(true))
-				// }
-			}
-		});
 	}
 	// console.log(xmlParse)
 	return new XMLSerializer().serializeToString(appoggianceFormate(xmlParse));
@@ -814,39 +506,6 @@ export const appoggianceFormate = (xmlParse: Document): Document => {
 	return xmlParse;
 };
 
-export const getVoicePartInfo = () => {
-	// const { MusicalInstrumentClassification, chinesePartName } = appState;
-	const { MusicalInstrumentClassification, chinesePartName } = { MusicalInstrumentClassification: {}, chinesePartName: [] as any[] };
-
-	let subjectId = -1;
-	const { partListNames, partIndex } = state;
-	const filterPartNames = partListNames.filter((item) => (item || "").trim() !== "");
-	if (filterPartNames.length) {
-		for (const Classification of Object.entries(MusicalInstrumentClassification)) {
-			const [key, value] = Classification as [string, string[]];
-			const activePart: any = partListNames[partIndex];
-			// console.log({activePart, value, partListNames})
-			const filterValue = value.filter((item) => item && (activePart || "").indexOf(item || "") > -1);
-			if (activePart && (filterValue.length || value.includes(activePart))) {
-				if (!isNaN(+key)) {
-					subjectId = +key;
-				}
-				return {
-					realPartListNames: partListNames,
-					subjectId: subjectId,
-					partListNames: value,
-					partName: activePart,
-					chinesePartName: chinesePartName[activePart] || activePart,
-				};
-			}
-		}
-	}
-	return {
-		subjectId: subjectId,
-		partListNames: [],
-	};
-};
-
 /** 根据ID获取最顶级ID */
 export const isWithinScope = (tree: any[], id: number): number => {
 	if (!tree) return 0;
@@ -867,51 +526,6 @@ export const isWithinScope = (tree: any[], id: number): number => {
 	return result;
 };
 
-/**
- * 特殊教材分类id
- */
-export const classids = [1, 30, 97]; // [大雅金唐, 竖笛教程, 声部训练]
-
-/**
- * 指定id是否在给定的类别中
- * @param tree
- * @param id
- * @param parentInTree default: false
- */
-export const idIsInClassIds = (tree: any[], id: number, parentInTree = false, targetIds = classids): boolean => {
-	if (!tree) return false;
-	let result = false;
-	for (const item of tree) {
-		// console.log(id, item.id, (parentInTree || classids.includes(item.id)))
-		if (item.id === id && (parentInTree || targetIds.includes(item.id))) {
-			result = true;
-			break;
-		}
-		if (item.sysMusicScoreCategoriesList) {
-			result = idIsInClassIds(item.sysMusicScoreCategoriesList, id, parentInTree || targetIds.includes(item.id), targetIds);
-			if (result) break;
-		}
-	}
-	// console.log('result', result)
-	return result;
-};
-
-/**
- * 获取图片地址
- * @param src 图片地址
- * @returns
- */
-const getImageSize = (src: string): Promise<HTMLImageElement> => {
-	return new Promise((resolve, reject) => {
-		const img = new Image();
-		img.src = src;
-		img.onload = () => {
-			resolve(img);
-		};
-		img.onerror = (err) => reject(err);
-	});
-};
-
 class NoteList {
 	notes: any[] = [];
 	constructor(notes: any[]) {
@@ -986,7 +600,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 	const allNotes: any[] = [];
 	const allNoteId: string[] = [];
 	const allMeasures: any[] = [];
-	const { speed: baseSpeed } = state;
+	const { originSpeed: baseSpeed } = state;
 	const formatRealKey = (realKey: number, detail: any) => {
 		// 长笛的LEVEL 2-5-1条练习是泛音练习,以每小节第一个音的指法为准,高音不变变指法。
 		const olnyOneIds = ["906"];
@@ -1264,6 +878,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			// console.log(activeInstantaneousTempo)
 			// console.log({vDenominator, NoteRealValue, denominator, numerator, wholeValue, realValue, vRealValue, measureSpeed, speed, beatSpeed})
 			// console.log(gradualLength)
+			const _noteLength = NoteRealValue
 			let noteLength = gradualLength ? gradualLength : Math.min(vRealValue, NoteRealValue) * formatBeatUnit(beatUnit) * (60 / beatSpeed);
 			const measureLength = vRealValue * vDenominator * (60 / beatSpeed);
 			// console.table({value: iterator.currentTimeStamp.realValue, vRealValue,NoteRealValue, noteLength,measureLength, MeasureNumberXML: note.sourceMeasure.MeasureNumberXML})
@@ -1271,8 +886,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			relaMeasureLength += noteLength;
 			let relaEndtime = noteLength + relativeTime;
 			const fixedKey = note.fixedKey || 0;
-			const svgElelent = activeVerticalMeasureList[0]?.vfVoices["1"]?.tickables[si];
-			// console.log(note.sourceMeasure.MeasureNumberXML,note,svgElelent, NoteRealValue, measureLength)
+			const svgElement = activeVerticalMeasureList[0]?.vfVoices["1"]?.tickables[si];
+			// console.log(note.sourceMeasure.MeasureNumberXML,note,svgElement, NoteRealValue, measureLength)
 			if (allNotes.length && allNotes[allNotes.length - 1].relativeTime === relativeTime) {
 				continue;
 			}
@@ -1292,9 +907,12 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			}
 			// console.log(note.tie)
 			const nodeDetail = {
+				isRestFlag: note.isRestFlag,
+				noteId: note.NoteToGraphicalNoteObjectId,
 				measureListIndex: note.sourceMeasure.measureListIndex,
 				MeasureNumberXML: note.sourceMeasure.MeasureNumberXML,
-				svgElement: svgElelent,
+				_noteLength: _noteLength,
+				svgElement: svgElement,
 				difftime,
 				octaveOffset: activeVerticalMeasureList[0]?.octaveOffset,
 				frequency: note.pitch?.frequency,
@@ -1308,7 +926,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				tempoInBPM: note.sourceMeasure.tempoInBPM,
 				measureLength,
 				relaMeasureLength,
-				id: svgElelent?.attrs.id,
+				id: svgElement?.attrs.id,
 				note: note.halfTone + 12, // see issue #224
 				relativeTime: retain(relativeTime),
 				time: retain(relativeTime + fixtime),
@@ -1317,7 +935,6 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				realValue,
 				halfTone: note.halfTone,
 				noteElement: note,
-				svgElelent,
 				fixedKey,
 				realKey: 0,
 				duration: 0,

+ 183 - 130
src/helpers/metronome.ts

@@ -4,16 +4,19 @@
  * time: 2022.11.14
  */
 import { reactive, watch } from "vue";
-import {tickUrl as tick, tockUrl as tock} from "../constant/audios";
-import { browser } from "../utils";
-import state from "../state";
+import { tickUrl as tick, tockUrl as tock } from "/src/constant/audios";
+import { browser } from "/src/utils/index";
+import state from "/src/state";
+import { Howl } from "howler";
+import tockAndTick from "/src/constant/tockAndTick.json";
 type IOptions = {
 	speed: number;
 };
-const ac = window.AudioContext || (window as any).webkitAudioContext || (window as any).mozAudioContext || (window as any).msAudioContext;
-const browserInfo = browser()
+const browserInfo = browser();
+
 export const metronomeData = reactive({
 	disable: true,
+	initPlayerState: false,
 	lineShow: false,
 	isClick: false,
 	metro: null as unknown as Metronome,
@@ -23,12 +26,26 @@ export const metronomeData = reactive({
 	activeIndex: null as unknown as number,
 	activeMetro: {} as any,
 });
+
+// 切换隐藏光标
+const toggleLine = () => {
+	if (!metronomeData.lineShow) return;
+	const img: HTMLElement = document.querySelector("#cursorImg-0")!;
+	if (img) {
+		if (metronomeData.activeMetro.isRestFlag) {
+			img.classList.remove("lineHide");
+		} else {
+			img.classList.add("lineHide");
+		}
+	}
+};
 watch(
 	() => metronomeData.lineShow,
 	() => {
 		const img: HTMLElement = document.querySelector("#cursorImg-0")!;
 		if (img) {
-			if (metronomeData.lineShow) {
+			const item = state.times[state.activeNoteIndex]
+			if (metronomeData.lineShow && !item.isRestFlag) {
 				img.classList.add("lineHide");
 			} else {
 				img.classList.remove("lineHide");
@@ -38,7 +55,6 @@ watch(
 );
 
 class Metronome {
-	ctx = new ac();
 	playType = "tick";
 	source = null as any; // 创建音频源头
 	source1 = null as any;
@@ -48,19 +64,20 @@ class Metronome {
 	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);
-		});
 	}
-
+	initPlayer() {
+		if (!this.source1) {
+			this.source1 = this.loadAudio1();
+		}
+		if (!this.source2) {
+			this.source2 = this.loadAudio2();
+		}
+		metronomeData.initPlayerState = true;
+	}
 
 	// 播放
 	sound = (currentTime: number) => {
-		// console.log("🚀 ~ currentTime", currentTime)
-		currentTime = setCurrentTime(currentTime)
+		currentTime = setCurrentTime(currentTime);
 		let index = -1;
 		let activeMetro = -1;
 		for (let i = 0; i < metronomeData.metroList.length; i++) {
@@ -81,42 +98,31 @@ class Metronome {
 			// console.log("🚀 ~ metronomeData.activeMetro",metronomeData.activeMetro.measureNumberIndex, metronomeData.activeMetro.index)
 			this.playAudio();
 			metronomeData.isClick = false;
+			toggleLine()
 			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); //立即播放
+		if (!metronomeData.initPlayerState) return;
+		this.source = metronomeData.activeMetro?.index === 0 ? this.source1 : this.source2;
+		this.source.volume(metronomeData.disable ? 0 : 0.4);
+		this.source.play();
 	};
 
 	// 切换
 	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;
+	loadAudio1 = () => {
+		return new Howl({
+			src: tockAndTick.tick,
 		});
-		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;
+	loadAudio2 = () => {
+		return new Howl({
+			src: tockAndTick.tock,
 		});
-		return audioBuffer;
 	};
 	getStep(time: number) {
 		for (let i = 0; i < metronomeData.metroMeasure.length; i++) {
@@ -153,35 +159,40 @@ class Metronome {
 						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[]
+						svgs: [] as any[],
+						isRestFlag: note.isRestFlag,
 					};
 					// 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
+					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
-							},[])
+								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)
+							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)
+							console.log(error);
 						}
 						m.stepList = calculateMutilpleMetroStep(note.measures, m);
 					} else {
@@ -202,27 +213,28 @@ class Metronome {
 				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 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 = '';
+					let left = "";
 					if (measure.stepList[j]) {
-						left = measure.stepList[j] + 'px'
+						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
+						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, 
+						// left: (measure.stepList[j] ? measure.stepList[j] + 'px' : (j + 1) * widthStep + '%'),
+						left: left?.indexOf("%") > -1 ? `calc(${left})` : left,
 						measureNumberXML: measure.measureNumberXML,
+						isRestFlag: measure.isRestFlag,
 					});
 				}
 			}
@@ -233,97 +245,138 @@ class Metronome {
 		// 5.得到所有的节拍时间
 		metronomeData.metroList = metroList;
 		metronomeData.metroMeasure = metroMeasure;
+		metronomeData.activeMetro = metroMeasure[0]?.[0] || {};
 	}
 }
 
 // 计算拍子的时值
 function calculateMetroStep(arr: any[], m: any): number[] {
-	const step = m.time / m.numerator;
+	const measureLength = arr.reduce((total: number, item: any) => {
+		total += item._noteLength;
+		return total;
+	}, 0);
+	const clap = measureLength / m.numerator;
 	if (arr.length === 1) {
-		if (arr[0].svgElelent && !arr[0].svgElelent.isRest()) {
-			let bbox = arr[0]?.svgElelent?.getBoundingBox() || { x: 0 };
-			return [(bbox.x - m.stave_x) * 0.7]
+		const wholeNote = arr[0].svgElement;
+		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++) {
+				stepList.push(bbox.x - measure_bbox.x + i * stepWidth);
+			}
+			// console.log("🚀 ~ stepList:", stepList, m.measureNumberXML)
+			return stepList;
 		}
-		return []
-	};
-	// console.log("🚀 ~ arr", arr, step, m.measureNumberXML);
-	let total = 0;
+		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];
-		let noteTime = item.endtime - item.time;
-		total += noteTime;
+		item.index = i;
+		const noteLength = item._noteLength;
+		totalLength += noteLength;
 		// 大于一拍
-		let bbox = item?.svgElelent?.getBoundingBox() || { x: 0 };
-		// console.log(item?.svgElelent?.attrs?.el)
-		if (noteTime > step) {
-			const exceedStep = Math.floor(noteTime / step)
-			for(let j = 0; j < exceedStep; j++){
-				// console.log('超过一拍了:' + Math.floor(noteTime / step), notes, m.measureNumberXML)
-				total -= step;
-				if (j === 0){
-					let x = (bbox.x - m.stave_x) * 0.7;
-					if (notes.length > 0) {
-						bbox = notes[0]?.svgElelent?.getBoundingBox() || { x: 0 };
-						x = (bbox.x - m.stave_x) * 0.7;
-					}
-					stepList.push(x);
-					notes = []
-				} else {
-					let x: any = undefined
-					stepList.push(x);
-				}
-				
-			}
-		} else {
-			notes.push(item);
-		}
-		// console.log(notes)
-		if (Math.abs(total - step) < 0.001) {
-			let x = (bbox.x - m.stave_x) * 0.7;
+		const exceedStep = Math.floor(totalLength / clap);
+		// console.log(`note`, item?.svgElement?.attrs?.el,notes.length,{noteLength, exceedStep,clap}, m.measureNumberXML)
+		if (exceedStep >= 1) {
+			totalLength -= clap;
+			// 一拍
+			const measure_bbox = item?.svgElement?.attrs?.el?.parentElement?.parentElement?.getBoundingClientRect?.() || { x: 0 };
 			if (notes.length > 0) {
-				bbox = notes[0]?.svgElelent?.getBoundingBox() || { x: 0 };
-				x = (bbox.x - m.stave_x) * 0.7;
+				let bbox = notes[0]?.svgElement?.attrs?.el?.getBoundingClientRect?.() || { x: 0 };
+				let x: any = bbox.x - measure_bbox.x;
+				if (notes[0]._noteLength / clap >= 1) {
+					const nextNote = arr[notes[0].index + 1]?.svgElement?.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]?.svgElement?.attrs?.el, arr[notes[0].index + 1]?.svgElement?.attrs?.el, bbox.x - nextNote.x, stepWidth, m.measureNumberXML);
+				}
+				// console.log(`一拍`, notes[0]?.svgElement?.attrs?.el, m.measureNumberXML, notes[0]._noteLength , clap, 'aa')
+				stepList.push(x);
+			} else {
+				let bbox = item?.svgElement?.attrs?.el?.getBoundingClientRect?.() || { x: 0 };
+				let x: any = bbox.x - measure_bbox.x;
+				// console.log(`一拍`, item?.svgElement?.attrs?.el, m.measureNumberXML)
+				stepList.push(x);
 			}
-			// console.log("一拍", notes, m.measureNumberXML);
-			stepList.push(x);
-			total = 0;
 			notes = [];
+			let bbox = item?.svgElement?.attrs?.el?.getBoundingClientRect?.() || { x: 0 };
+			let x: any = bbox.x - measure_bbox.x;
+			let stepWidth = 0;
+			if (exceedStep > 1) {
+				// 二拍以上
+				const nextNote = arr[i + 1]?.svgElement?.attrs?.el?.getBoundingClientRect?.() || { x: measure_bbox.right } || { x: 0 };
+				stepWidth = Math.abs(bbox.x - nextNote.x) / exceedStep;
+				// console.log("二拍以上 ~ nextNote:",bbox.x , nextNote.x,stepWidth, item?.svgElement?.attrs?.el,arr[i + 1]?.svgElement?.attrs?.el, exceedStep);
+			}
+
+			for (let j = 1; j < exceedStep; j++) {
+				totalLength -= clap;
+				// console.log(`超一拍`,item?.svgElement?.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)
+		if (list.includes(n)) {
+			list.push(undefined as any);
 		} else {
-			list.push(n)
+			list.push(n);
 		}
-		return list
-	}, [])
-	// console.log('stepList', [...stepList], m.measureNumberXML)
+		return list;
+	}, []);
+	// console.log("stepList", [...stepList], m.measureNumberXML);
 	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}
+	const measure_bbox = arr[0]?.svgElement?.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 [bbox.x - measure_bbox.x];
 		}
-		return []
-	};
+		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
+		item._index = i;
 		const noteTime = item.endtime - item.time;
 		total += noteTime;
 		let svgEle = m.svgs[i]?.attrs?.el;
@@ -335,12 +388,12 @@ function calculateMutilpleMetroStep(arr: any[], m: any): number[] {
 			// console.log('超过一拍了', notes, m.measureNumberXML)
 			let x = bbox.x - measure_bbox.x;
 			if (notes.length > 0) {
-				svgEle = m.svgs[notes[0]._index]?.attrs?.el
+				svgEle = m.svgs[notes[0]._index]?.attrs?.el;
 				bbox = svgEle?.getBoundingClientRect?.() || { x: 0 };
 				x = bbox.x - measure_bbox.x;
 			}
 			stepList.push(x);
-			notes = []
+			notes = [];
 		} else {
 			notes.push(item);
 		}
@@ -348,7 +401,7 @@ function calculateMutilpleMetroStep(arr: any[], m: any): number[] {
 		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
+				svgEle = m.svgs[notes[0]._index]?.attrs?.el;
 				bbox = svgEle?.getBoundingClientRect?.() || { x: 0 };
 				x = bbox.x - measure_bbox.x;
 			}
@@ -359,27 +412,27 @@ function calculateMutilpleMetroStep(arr: any[], m: any): number[] {
 		}
 	}
 	stepList = stepList.reduce((list: any[], n: number) => {
-		if (list.includes(n)){
-			list.push(undefined as any)
+		if (list.includes(n)) {
+			list.push(undefined as any);
 		} else {
-			list.push(n)
+			list.push(n);
 		}
-		return list
-	}, [])  //Array.from(new Set(stepList))
+		return list;
+	}, []); //Array.from(new Set(stepList))
 	// console.log('stepList', stepList, m.measureNumberXML)
 	return stepList;
 }
 
 // 延迟兼容处理
-function setCurrentTime(time: number){
-	if (browserInfo.huawei || browserInfo.xiaomi){
-		time += 0.125
+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
+		time += 0.11;
+	} else if (browserInfo.ios) {
+		time += 0.01;
 	}
-	return time
+	return time;
 }
 
 export default Metronome;

+ 13 - 11
src/page-gym/detail/index.tsx

@@ -38,7 +38,6 @@ export default defineComponent({
 			setState(musicInfo, index);
 			setCustom();
 			detailData.isLoading = false;
-			// initAudioEvent()
 		};
 		const getCategorId = (arr: any[], key = "sysMusicScoreCategoriesList"): any[] => {
 			const list = [];
@@ -66,15 +65,11 @@ export default defineComponent({
 		/** 获取分类数据 */
 		const getCategory = async () => {
 			const res = await sysMusicScoreCategoriesQueryTree(storeData.platformType === "WEB");
-			console.log("特殊曲谱分类ids:");
-			console.log(getIds(res.data).toString());
+			// console.log("特殊曲谱分类ids:");
+			// console.log(getIds(res.data).toString());
 		};
 
-		const setCustom = () => {
-			if (state.extConfigJson.multitrack) {
-				setGlobalData("multitrack", state.extConfigJson.multitrack);
-			}
-		};
+		
 		const setState = (data: any, index: number) => {
 			state.xmlUrl = data.xmlUrl;
 			state.partIndex = index;
@@ -100,8 +95,15 @@ export default defineComponent({
 			state.midiUrl = data.midiUrl;
 			state.parentCategoriesId = data.parentCategoriesId;
 			state.playMode = data.playMode;
-			state.speed = data.speed;
+			state.originSpeed = state.speed = data.speed;
 			state.track = data.track;
+			state.isOpenPrepare = true
+		};
+
+		const setCustom = () => {
+			if (state.extConfigJson.multitrack) {
+				setGlobalData("multitrack", state.extConfigJson.multitrack);
+			}
 		};
 
 		onMounted(async () => {
@@ -117,7 +119,7 @@ export default defineComponent({
 			state.times = formateTimes(osmd);
 			console.log("🚀 ~ state.times:", state.times);
 			try {
-				metronomeData.metro = new Metronome({ speed: state.activeSpeed });
+				metronomeData.metro = new Metronome();
 				metronomeData.metro.init(state.times);
 			} catch (error) {}
 			detailData.showSelection = true;
@@ -132,7 +134,7 @@ export default defineComponent({
 				<div class={styles.headHeight}>
 					<Transition name="van-slide-down">{!detailData.isRenderLoading && <HeaderTop />}</Transition>
 				</div>
-				<div class={styles.container}>
+				<div class={styles.container} id="mainContainer">
 					{!detailData.isLoading && (
 						<div class={styles.musicContainer}>
 							<MusicScore onRendered={handleRendered} />

+ 14 - 35
src/page-gym/header-top/index.tsx

@@ -1,24 +1,23 @@
-import { computed, defineComponent, reactive, ref } from "vue";
+import { computed, defineComponent, onMounted, reactive, ref } from "vue";
 import styles from "./index.module.less";
 
 import iconBack from "./image/icon-back.svg";
 import Title from "./title";
-import state, { togglePlay } from "../../state";
+import state, { handleChangeSection, handleResetPlay, togglePlay } from "../../state";
 import { headImg } from "./image";
 import { useRect } from "@vant/use";
 import { Badge, Circle, Popover } from "vant";
 import { metronomeData } from "../../helpers/metronome";
 import Speed from "./speed";
 
+export const headData = reactive({
+	speedShow: false,
+});
+
 export default defineComponent({
 	name: "header-top",
 	setup() {
 		const headRef = ref();
-		const headRect = useRect(headRef.value);
-
-		const headData = reactive({
-			speedShow: false,
-		});
 
 		return () => (
 			<div ref={headRef} class={styles.headerTop}>
@@ -33,19 +32,17 @@ export default defineComponent({
 						{/* <Button class={styles.button} icon={runtime.evaluatingStatus ? Icons.evaluating2 : Icons.evaluating} disabled={(this.isAppPlay ? !state.activeDetail?.midiUrl || state.midiPlayIniting : !(runtime.songs.background || runtime.songs.music)) || isHomework || !state.enableEvaluation} onClick={this.authBefore("evaluating", this.evaluating)} /> */}
 						<span>评测</span>
 					</div>
-					<div class={styles.btn} id="tips-step-4">
+					<div class={styles.btn} id="tips-step-4" onClick={() => handleChangeSection()}>
 						<img class={styles.iconBtn} src={headImg(`section${state.section.length}.svg`)} />
 						{/* <Button class={styles.button} icon={Icons["section" + state.section.length]} color="#01C1B5" disabled={runtime.isFirstPlay || runtime.evaluatingStatus || isHomework} onClick={this.authBefore("excerpts", RuntimeUtils.sectionChange)} /> */}
 						<span>选段</span>
 					</div>
 					<div class={styles.btn} id="tips-step-5" onClick={() => togglePlay()}>
 						<div class={styles.btnWrap}>
-							<img style={{ marginTop: '-1px', display: state.playState === "paused" ? "block" : "none" }} class={styles.iconBtn} src={headImg("icon-play.svg")} />
-							<img style={{ marginTop: '-1px', display: state.playState !== "paused" ? "block" : "none" }} class={styles.iconBtn} src={headImg("icon-pause.svg")} />
+							<img style={{ marginTop: "-1px", display: state.playState === "paused" ? "block" : "none" }} class={styles.iconBtn} src={headImg("icon-play.svg")} />
+							<img style={{ marginTop: "-1px", display: state.playState !== "paused" ? "block" : "none" }} class={styles.iconBtn} src={headImg("icon-pause.svg")} />
 							<Circle class={styles.progress} stroke-width={80} currentRate={state.playProgress} rate={100} layerColor="#01C1B5" color="#FFC830" />
 						</div>
-						{/* <Button class={styles.button} icon={runtime.playState === "play" || runtime.metroing ? Icons.pause : Icons.play} disabled={this.playerButtonIsDisabled} color={runtime.loading ? "#01C1B5" : ""} />
-						<Circle class={classnames(styles.circle, styles.button)} stroke-width={80} currentRate={progress} rate={100} layerColor="#01C1B5" color="#FFC830" /> */}
 						<span>{state.playState === "play" ? "暂停" : "播放"}</span>
 					</div>
 					<div
@@ -57,14 +54,6 @@ export default defineComponent({
 					>
 						<img style={{ display: state.playSource === "music" ? "block" : "none" }} class={styles.iconBtn} src={headImg("icon-music.svg")} />
 						<img style={{ display: state.playSource !== "music" ? "block" : "none" }} class={styles.iconBtn} src={headImg("icon-background.svg")} />
-						{/* <Button
-							class={styles.button}
-							disabled={this.changeModeIsDisabled || isHomework}
-							icon={runtime.mode === "background" ? Icons.background : Icons.music}
-							onClick={this.authBefore("switch", () => {
-								RuntimeUtils.changeMode(runtime.mode === "background" ? "music" : "background");
-							})}
-						/> */}
 						<span>{state.playSource === "music" ? "原声" : "伴奏"}</span>
 					</div>
 					<div
@@ -80,26 +69,15 @@ export default defineComponent({
 						class={styles.btn}
 						onClick={async () => {
 							metronomeData.disable = !metronomeData.disable;
+							metronomeData.metro?.initPlayer()
 						}}
 					>
 						<img style={{ display: metronomeData.disable ? "block" : "none" }} class={styles.iconBtn} src={headImg("tickoff.png")} />
 						<img style={{ display: !metronomeData.disable ? "block" : "none" }} class={styles.iconBtn} src={headImg("tickon.png")} />
 						<span style={{ whiteSpace: "nowrap" }}>节拍器</span>
 					</div>
-					<div class={styles.btn} id="tips-step-7">
+					<div class={styles.btn} id="tips-step-7" onClick={() => handleResetPlay()}>
 						<img class={styles.iconBtn} src={headImg("replay.svg")} />
-						{/* <Button
-							class={styles.button}
-							icon={Icons.replay}
-							disabled={runtime.isFirstPlay || runtime.evaluatingStatus || isHomework}
-							onClick={this.authBefore("follow", async () => {
-								if (state.activeTick > -1) {
-									return;
-								}
-								RuntimeUtils.setCurrentTime(0);
-								RuntimeUtils.ended(new Event("ended"));
-							})}
-						/> */}
 						<span>重播</span>
 					</div>
 
@@ -109,11 +87,12 @@ export default defineComponent({
 								<div
 									id="tips-step-8"
 									class={[styles.btn]}
-									onClick={() => {
+									onClick={(e: Event) => {
+										e.stopPropagation()
 										headData.speedShow = !headData.speedShow;
 									}}
 								>
-									<Badge class={styles.badge} content={120}>
+									<Badge class={styles.badge} content={state.speed}>
 										<img class={styles.iconBtn} src={headImg("speed.svg")} />
 									</Badge>
 									<span>速度</span>

+ 25 - 63
src/page-gym/header-top/speed/index.tsx

@@ -1,86 +1,48 @@
-import { defineComponent, reactive } from "vue";
+import { defineComponent, reactive, ref, watch } from "vue";
 import { Button, Slider } from "vant";
 import styles from "./index.module.less";
 import { headImg } from "../image";
-import state from "../../../state";
+import state, { handleSetPlaybackRate } from "../../../state";
+import { useClickAway } from "@vant/use";
+import { headData } from "..";
 
 export default defineComponent({
 	name: "speed",
-	props: {
-		changed: {
-			type: Function,
-			default: (speed: number) => {},
-		},
-		updateSpeed: {
-			type: Function,
-			default: (speed: number) => {},
-		},
-		mode: {
-			type: String,
-		},
-		changeMode: {
-			type: Function,
-			default: (val: string) => {},
-		},
-		lib: {
-			type: Object,
-		},
-	},
 	setup(props) {
-		state.speed = props.lib?.speed;
-        const speed = reactive({
-            value: 70
-        })
-		const changeSpeed = (speed: number) => {
-			state.speed = speed;
-			props.changed(state.speed);
-		};
-
-		const updateSpeed = (speed: number) => {
-			state.speed = speed;
-			props.updateSpeed(state.speed);
-		};
+		const speed = reactive({
+			value: state.speed,
+		});
 
 		const minusSpeed = () => {
-
 			speed.value = Math.max(speed.value - 1, 45);
-			state.speed = Math.max(state.speed - 1, 45);
-			props.changed(state.speed);
 		};
 
 		const plusSpeed = () => {
-            speed.value = Math.min(speed.value + 1, 270)
-			state.speed = Math.min(state.speed + 1, 270);
-			props.changed(state.speed);
+			speed.value = Math.min(speed.value + 1, 270);
 		};
 
+		watch(() => speed.value, () => {
+			const playbackRate = speed.value / state.originSpeed
+			// console.log(state.originSpeed, state.speed)
+			state.speed = speed.value
+			handleSetPlaybackRate(playbackRate)
+		})
+
+		const speedRef = ref();
+		useClickAway(speedRef, () => {
+			headData.speedShow = false
+		});
+
 		return () => (
-			<div class={styles.speedContainer}>
+			<div class={styles.speedContainer} ref={speedRef}>
 				<Button class={styles.btn} icon={headImg("plus.png")} disabled={state.speed == 270} onClick={plusSpeed} />
-				<Slider
-					class={styles.slider}
-					max={270}
-					min={45}
-					vertical
-                    v-model={speed.value}
-                    reverse
-				>
+				<Slider class={styles.slider} max={270} min={45} vertical v-model={speed.value} reverse>
 					{{
-                        button: () => <div class={styles.customButton}>{speed.value}</div>
-                    }}
+						button: () => <div class={styles.customButton}>{speed.value}</div>,
+					}}
 				</Slider>
 				<Button class={styles.btn} icon={headImg("minus.png")} disabled={state.speed == 45} onClick={minusSpeed} />
 			</div>
 		);
-	},
-	methods: {
-		resetSpeed() {
-			state.speed = this.lib?.speed;
-			this.changed(this.lib?.speed);
-		},
-		refUpdateSpeed(speed: number) {
-			state.speed = speed;
-			this.updateSpeed(state.speed);
-		},
-	},
+	}
 });

+ 185 - 43
src/state.ts

@@ -1,6 +1,7 @@
-import { Toast } from "vant";
+import { showToast, Toast } from "vant";
 import { reactive, watchEffect } from "vue";
 import { OpenSheetMusicDisplay } from "../osmd-extended/src";
+import { metronomeData } from "./helpers/metronome";
 import { GradualNote, GradualTimes, GradualVersion, IMode } from "./type";
 
 const state = reactive({
@@ -36,6 +37,8 @@ const state = reactive({
 	playMode: "MP3",
 	/** 后台设置速度 */
 	speed: 0,
+	/** 记录设置的速度 */
+	originSpeed: 0,
 	/** 分轨名称 */
 	track: "",
 	/** 当前显示声部索引 */
@@ -46,8 +49,6 @@ const state = reactive({
 	osmd: null as unknown as OpenSheetMusicDisplay,
 	/**是否是特殊乐谱类型, 主要针对管乐迷  */
 	isSpecialBookCategory: false,
-	/** 选段数据 */
-	section: [] as any[],
 	/** 播放状态 */
 	playState: "paused" as "play" | "paused",
 	/** 原音,伴奏 */
@@ -62,14 +63,30 @@ const state = reactive({
 	songEl: null as unknown as HTMLAudioElement,
 	/** 背景音乐ref */
 	backgroundEl: null as unknown as HTMLAudioElement,
+	/** 选段状态 */
+	sectionStatus: false,
+	/** 选段数据 */
+	section: [] as any[],
+	/** 选段提示 */
+	sectionToast: null as any,
+	/** 选段背景 */
+	sectionBoundingBoxs: [] as any[],
+	/** 开启选段预备 */
+	isOpenPrepare: false,
+	/** 选段预备 */
+	sectionFirst: null as any,
+	/** 音符数据 */
+	times: [] as any[],
+	/** 重复自动播放 */
+	repeatAutoPlay: true,
+	/** 播放模式 */
+	modeType: "practise" as "practise" | "follow" | "evaluating",
 
 	repeatedBeats: 0,
 
-	sectionStatus: false,
 	maskStatus: false,
-	times: [] as any[],
 	timesById: {} as any,
-	sectionBoundingBoxs: [] as any[],
+
 	activeTick: -1,
 	activeTickRepeat: 1,
 	showTick: false,
@@ -122,12 +139,7 @@ const setStep = () => {
 			if (state.playState !== "play") {
 				return;
 			}
-			if (state.songEl) {
-				state.playProgress = (state.songEl.currentTime / state.songEl.duration) * 100;
-				// console.log("🚀 ~ state.playProgress:", state.playProgress);
-				const item = getNote(state.songEl.currentTime);
-				if (item) gotoNext(item);
-			}
+			handlePlaying();
 		}
 		setStep();
 	});
@@ -140,13 +152,62 @@ export const onPlay = () => {
 export const onTimeupdate = (evt: Event) => {};
 /** 播放完成事件 */
 export const onEnded = () => {
-	state.playState = "paused";
+	handleStopPlay();
 };
 
-/** 切换播放 */
-export const togglePlay = () => {
-	state.playState = state.playState === "paused" ? "play" : "paused";
+/**
+ * 播放一直触发的事件
+ */
+const handlePlaying = (_item?: any) => {
+	if (state.songEl) {
+		const currentTime = state.songEl.currentTime;
+		state.playProgress = (currentTime / state.songEl.duration) * 100;
+		const item = _item ? _item : getNote(currentTime);
+
+		if (item) {
+			// 选段状态下
+			if (state.sectionStatus && state.section.length === 2) {
+				let startItemIndex = state.section[0].index
+				let startXmlIndex = state.section[0].MeasureNumberXML
+				// 开启预备拍
+				if (state.sectionFirst) {
+					startItemIndex = state.sectionFirst.i
+					startXmlIndex = state.sectionFirst.MeasureNumberXML
+				}
+				if (item.MeasureNumberXML < startXmlIndex || item.MeasureNumberXML > state.section[1].MeasureNumberXML) {
+					// console.log('选段播放结束')
+					skipNotePlay(startItemIndex);
+					return;
+				}
+				
+			}
+			gotoNext(item);
+		}
+
+		metronomeData.metro?.sound(currentTime);
+	}
+};
+/** 跳转到指定音符开始播放 */
+export const skipNotePlay = (itemIndex: number) => {
+	const item = state.times[itemIndex];
+	if (item) {
+		state.songEl && (state.songEl.currentTime = item.time);
+		state.backgroundEl && (state.backgroundEl.currentTime = item.time);
+		handlePlaying(item);
+	}
+};
+
+/**
+ * 切换曲谱播放状态
+ * @param playState 可选: 默认 undefined, 需要切换的状态 play:播放, paused: 暂停
+ */
+export const togglePlay = (playState?: "play" | "paused") => {
+	state.playState = playState ? playState : state.playState === "paused" ? "play" : "paused";
 	if (state.playState == "play") {
+		// 如果没有选段结束开始播放,清空选段状态
+		if (state.sectionStatus && state.section.length < 2) {
+			clearSelection();
+		}
 		state.songEl?.play();
 		state.backgroundEl?.play();
 	} else {
@@ -159,7 +220,14 @@ const handleStopPlay = () => {
 	state.playState = "paused";
 	state.songEl?.pause();
 	state.backgroundEl?.pause();
-	skipNotePlay(0)
+	skipNotePlay(0);
+	// 重复自动播放如果为开启,自动开始播放, 且是练习模式
+	if (state.repeatAutoPlay && state.modeType === "practise") {
+		scrollViewNote();
+		setTimeout(() => {
+			togglePlay("play");
+		}, 1000);
+	}
 };
 
 /** 跳转到指定音符 */
@@ -173,32 +241,33 @@ export const gotoCustomNote = (index: number) => {
 };
 /** 跳转到下一个音符 */
 export const gotoNext = (note: any) => {
-	if (note) {
-		const num = note.i;
-		// console.log('next', state.activeNoteIndex, num)
-		if (state.activeNoteIndex === note.i) return;
-		const osmd = state.osmd;
-		let prev = state.activeNoteIndex;
-		state.activeNoteIndex = num;
-		state.activeMeasureIndex = note.MeasureNumberXML;
-		if (prev && num - prev === 1) {
+	const num = note.i;
+	// console.log('next', state.activeNoteIndex, num)
+	if (state.activeNoteIndex === note.i) return;
+	const osmd = state.osmd;
+	let prev = state.activeNoteIndex;
+	state.activeNoteIndex = num;
+	state.activeMeasureIndex = note.MeasureNumberXML;
+
+	if (prev && num - prev === 1) {
+		osmd.cursor.next();
+	} else if (prev && num - prev > 0) {
+		while (num - prev > 0) {
+			prev++;
 			osmd.cursor.next();
-		} else if (prev && num - prev > 0) {
-			while (num - prev > 0) {
-				prev++;
-				osmd.cursor.next();
-			}
-		} else {
-			gotoCustomNote(num);
 		}
+	} else {
+		gotoCustomNote(num);
 	}
+
+	scrollViewNote();
 };
 /** 获取指定音符 */
 export const getNote = (currentTime: number) => {
 	const times = state.times;
 	const len = state.times.length;
 	/** 播放超过了最后一个音符的时间,直接结束 */
-	if (currentTime >= times[len - 1]) {
+	if (currentTime > times[len - 1].endtime + 1) {
 		handleStopPlay();
 		return;
 	}
@@ -217,19 +286,92 @@ export const getNote = (currentTime: number) => {
 	// console.log("activeNoteIndex", currentTime, state.activeNoteIndex, _item.i);
 	return _item;
 };
-/** 跳转到指定音符开始播放 */
-export const skipNotePlay = (itemIndex: number) => {
-	const item = state.times[itemIndex];
-	console.log("🚀 ~ item:", itemIndex);
+
+/** 重播 */
+export const handleResetPlay = () => {
+	skipNotePlay(0);
+};
+/** 设置速度 */
+export const handleSetPlaybackRate = (playbackRate: number) => {
 	if (state.songEl) {
-		state.songEl.currentTime = item.time;
+		state.songEl.playbackRate = playbackRate;
 	}
 	if (state.backgroundEl) {
-		state.backgroundEl.currentTime = item.time;
+		state.backgroundEl.playbackRate = playbackRate;
 	}
-	state.activeNoteIndex = item.i;
-	state.activeMeasureIndex = item.MeasureNumberXML;
-	gotoCustomNote(item.i);
+};
+/** 清除选段状态 */
+export const clearSelection = () => {
+	state.sectionStatus = false;
+	state.section = [];
+	state.sectionToast?.close();
+	state.sectionToast = null;
 };
 
+/** 开启选段 */
+export const handleChangeSection = () => {
+	// 如果开启了选段,再次点击取消选段
+	if (state.sectionStatus) {
+		clearSelection();
+		return;
+	}
+	state.sectionStatus = true;
+	// 开启
+	if (state.sectionStatus) {
+		togglePlay("paused");
+	}
+	state.sectionToast = showToast({
+		message: "请选择开始小节",
+		duration: 0,
+		position: "top",
+		className: "selectionToast",
+	});
+};
+
+/** 选择选段 */
+export const handleSelection = (item: any) => {
+	if (!state.sectionStatus || state.section.length > 1) return;
+	if (state.section.length !== 2 && item) {
+		state.section.push(item);
+		if (state.section.length === 2) {
+			state.section = state.section.sort((a, b) => a.time - b.time);
+			let startItemINdex = state.section[0].index
+			// 开启预备拍
+			if (state.isOpenPrepare) {
+				const startXmlIndex = state.section[0].MeasureNumberXML
+				state.sectionFirst = state.times.find((n: any) => (startXmlIndex - n.MeasureNumberXML) === 1)
+				startItemINdex = state.sectionFirst ? state.sectionFirst.i : startItemINdex
+			}
+			skipNotePlay(startItemINdex);
+			state.sectionToast?.close();
+			state.sectionToast = null;
+			
+		}
+	}
+	if (state.section.length === 1 && state.sectionToast) {
+		state.sectionToast.message = "请选择结束小节";
+	}
+};
+
+/**
+ * 窗口内滚动到音符的区域
+ * @param isScroll 可选: 强制滚动到顶部, 默认: false
+ * @returns void
+ */
+export const scrollViewNote = () => {
+	const cursorElement = document.getElementById("cursorImg-0")!;
+	const mainContainer = document.getElementById("mainContainer")!;
+	if (!cursorElement && !mainContainer) return;
+	if (cursorElement.offsetTop > 50) {
+		mainContainer.scrollTo({
+			top: cursorElement.offsetTop - 25,
+			behavior: "smooth",
+		});
+	} else {
+		mainContainer.scrollTo({
+			top: 0,
+			behavior: "smooth",
+		});
+	}
+};
 export default state;

+ 3 - 0
src/style.css

@@ -2,4 +2,7 @@
   margin: 0;
   padding: 0;
   box-sizing: border-box;
+}
+.selectionToast{
+  top: 10vh;
 }

+ 1 - 1
src/view/audio-list/index.module.less

@@ -3,5 +3,5 @@
     left: 0;
     bottom: 0;
     width: 100%;
-    z-index: -1000;
+    z-index: 1000;
 }

+ 25 - 3
src/view/audio-list/index.tsx

@@ -1,6 +1,8 @@
-import { computed, defineComponent, reactive } from "vue";
+import { computed, defineComponent, onMounted, reactive } from "vue";
 import state, { onEnded, onLoadedmetadata, onPlay, onTimeupdate } from "../../state";
 import styles from "./index.module.less";
+import { Howl, Howler } from "howler";
+import tockAndTick from '/src/constant/tockAndTick.json'
 
 export default defineComponent({
 	name: "audio-list",
@@ -9,10 +11,30 @@ export default defineComponent({
 		const isMusicMuted = computed(() => {
 			return state.playSource === "music";
 		});
+		
+		// const music = new Howl({
+		// 	src: tockAndTick.tick,
+		// })
+		// const accompany = new Howl({
+		// 	src: tockAndTick.tock,
+		// })
+		// console.log(state.music, music);
 		return () => (
 			<div class={styles.audioList}>
-				<audio muted={!isMusicMuted.value} preload="auto" ref={(el) => (state.songEl = el as HTMLAudioElement)} controls src={state.music} onLoadedmetadata={onLoadedmetadata} onPlay={onPlay} onTimeupdate={onTimeupdate} onEnded={onEnded} />
-				<audio muted={isMusicMuted.value} preload="auto" ref={(el) => (state.backgroundEl = el as HTMLAudioElement)} controls src={state.accompany} />
+				{/* <button onClick={() => {
+					music.volume(0)
+					music.play()
+					console.log("🚀 ~ music:", music)
+				}}>播放</button>
+				 <button onClick={() => {
+					accompany.play()
+				}}>播放</button> */}
+				{/*<button onClick={() => {
+					Howler.mute(true)
+					console.log("🚀 ~ music:", music, music.duration(), Howler.ctx.currentTime)
+				}}>静音</button> */}
+				<audio muted={!isMusicMuted.value} preload="auto" ref={(el) => (state.songEl = el as HTMLAudioElement)} src={state.music} onLoadedmetadata={onLoadedmetadata} onPlay={onPlay} onTimeupdate={onTimeupdate} onEnded={onEnded} />
+				<audio muted={isMusicMuted.value} preload="auto" ref={(el) => (state.backgroundEl = el as HTMLAudioElement)} src={state.accompany} />
 			</div>
 		);
 	},

+ 1 - 1
src/view/music-score/index.tsx

@@ -28,7 +28,7 @@ export default defineComponent({
 				drawSubtitle: false,
 				drawMeasureNumbers: false,
 				autoResize: false,
-				followCursor: false,
+				// followCursor: false,
 				drawPartNames: false, // 是否渲染声部
 				drawComposer: false, // 渲染作者
 				// autoBeam: true,

+ 72 - 0
src/view/selection/index.module.less

@@ -13,4 +13,76 @@
 }
 .staveBox{
     background-color: rgba(1, 193, 181, 0.2);
+}
+.leftStaveBox{
+    background-color: rgba(1, 193, 181, 0.2);
+    &::before{
+        content: '';
+        position: absolute;
+        left: -5px;
+        top: -5px;
+        width: 5px;
+        height: 100%;
+        border-top: 5px solid var(--van-primary-color);
+        border-left: 5px solid var(--van-primary-color);
+        border-bottom: 5px solid var(--van-primary-color);
+    }
+}
+.rightStaveBox{
+    background-color: rgba(1, 193, 181, 0.2);
+    &::after{
+        content: '';
+        position: absolute;
+        right: -5px;
+        top: -5px;
+        width: 5px;
+        height: 100%;
+        border-top: 5px solid var(--van-primary-color);
+        border-right: 5px solid var(--van-primary-color);
+        border-bottom: 5px solid var(--van-primary-color);
+    }
+}
+.centerStaveBox{
+    background-color: rgba(1, 193, 181, 0.2);
+    &::before{
+        content: '';
+        position: absolute;
+        left: -5px;
+        top: -5px;
+        width: 5px;
+        height: 100%;
+        border-top: 5px solid var(--van-primary-color);
+        border-left: 5px solid var(--van-primary-color);
+        border-bottom: 5px solid var(--van-primary-color);
+    }
+    &::after{
+        content: '';
+        position: absolute;
+        right: -5px;
+        top: -5px;
+        width: 5px;
+        height: 100%;
+        border-top: 5px solid var(--van-primary-color);
+        border-right: 5px solid var(--van-primary-color);
+        border-bottom: 5px solid var(--van-primary-color);
+    }
+}
+.prepareStaveBox{
+    background-color: rgba(255, 98, 37, 0.18);
+}
+.disable{
+    pointer-events: none;
+}
+
+.line {
+	position: absolute;
+	top: -20%;
+	height: 120%;
+	width: 4px;
+	background-color: #ff8d2960;
+}
+:global{
+    .lineHide{
+        opacity: 0;
+    }
 }

+ 64 - 31
src/view/selection/index.tsx

@@ -1,45 +1,52 @@
 import { computed, defineComponent, onMounted, reactive } from "vue";
-import state, { gotoCustomNote, skipNotePlay } from "/src/state";
+import state, { handleSelection, skipNotePlay } from "/src/state";
 import styles from "./index.module.less";
+import { metronomeData } from "/src/helpers/metronome";
 
 export default defineComponent({
 	name: "selection",
 	setup() {
 		const selectData = reactive({
 			notes: [] as any[],
+			staves: [] as any[],
 		});
 		const calcNoteData = () => {
 			const musicContainer = document.getElementById("musicContainer")?.getBoundingClientRect() || { x: 0, y: 0 };
 			const parentLeft = musicContainer.x || 0;
 			const parentTop = musicContainer.y || 0;
 			const notes = state.times;
-			const notesList: string[] = []
+			const notesList: string[] = [];
 			const MeasureNumberXMLList: number[] = [];
 			for (let i = 0; i < notes.length; i++) {
 				const item = notes[i];
 				// console.log("🚀 ~ item:", item)
 				const noteItem = {
-					index: i,
+					index: item.i,
 					MeasureNumberXML: item.MeasureNumberXML,
 					bbox: null as any,
 					staveBox: null as any,
+					isRestFlag: item.isRestFlag, // 是否休止符
 				};
-				if (item.svgElement) {
-					const noteEle = document.querySelector(`#vf-${item.svgElement?.attrs?.id}`);
-					const noteEleBox = item.svgElement.getBoundingBox?.() || { h: 40 };
-					// console.log("🚀 ~ noteEle:", noteEle, item.svgElement, noteEleBox.h)
-					if (noteEle) {
-						const noteBbox = noteEle.getBoundingClientRect?.() || { x: 0, width: 0 };
-						noteItem.bbox = {
-							left: noteBbox.x - parentLeft + "px",
-							top: noteBbox.y - parentTop + "px",
-							width: noteBbox.width + "px",
-							height: noteEleBox.h * state.zoom + "px",
-						};
+				if (!notesList.includes(item.noteId)) {
+					if (item.svgElement) {
+						const noteEle = document.querySelector(`#vf-${item.svgElement?.attrs?.id}`);
+						const noteEleBox = item.svgElement.getBoundingBox?.() || { h: 40 };
+						// console.log("🚀 ~ noteEle:", noteEle, item.svgElement, noteEleBox.h)
+						if (noteEle) {
+							const noteBbox = noteEle.getBoundingClientRect?.() || { x: 0, width: 0 };
+							noteItem.bbox = {
+								left: noteBbox.x - parentLeft + "px",
+								top: noteBbox.y - parentTop + "px",
+								width: noteBbox.width + "px",
+								height: noteEleBox.h * state.zoom + "px",
+							};
+						}
+						selectData.notes.push(noteItem);
+						notesList.push(item.noteId);
 					}
 				}
+
 				if (!MeasureNumberXMLList.includes(item.MeasureNumberXML)) {
-					// console.log(item);
 					if (item.stave) {
 						if (item.stave?.attrs?.id) {
 							const staveEle = document.querySelector(`#${item.stave.attrs.id}`);
@@ -50,39 +57,65 @@ export default defineComponent({
 								width: staveBbox.width + "px",
 								height: 50 * state.zoom + "px",
 							};
+							selectData.staves.push(noteItem);
 						}
 						MeasureNumberXMLList.push(item.MeasureNumberXML);
 					}
 				}
-
-				if (!notesList.includes(item.id)) {
-					selectData.notes.push(noteItem);
-					notesList.push(item.id)
-				}
 			}
-			console.log("🚀 ~ selectData.notes:", selectData.notes);
+			// console.log("🚀 ~ selectData.notes:", selectData.notes);
 		};
-		const staveBoxList = computed(() => {
-			return selectData.notes.filter((item) => item.staveBox);
+		const showClass = computed(() => {
+			return (item: any) => {
+				if (state.sectionStatus) {
+					if (state.section.length === 1) {
+						if (item.MeasureNumberXML == state.section[0].MeasureNumberXML) {
+							return styles.leftStaveBox;
+						}
+					}
+					if (state.section.length === 2) {
+						// 选段预备拍背景
+						if (state.sectionFirst && item.MeasureNumberXML === state.sectionFirst.MeasureNumberXML){
+							return styles.prepareStaveBox
+						}
+						if (item.MeasureNumberXML >= state.section[0].MeasureNumberXML && item.MeasureNumberXML <= state.section[1].MeasureNumberXML) {
+							if (item.MeasureNumberXML == state.section[0].MeasureNumberXML && item.MeasureNumberXML == state.section[1].MeasureNumberXML) {
+								return styles.centerStaveBox;
+							}
+							if (item.MeasureNumberXML == state.section[0].MeasureNumberXML) {
+								return styles.leftStaveBox;
+							}
+							if (item.MeasureNumberXML == state.section[1].MeasureNumberXML) {
+								return styles.rightStaveBox;
+							}
+							return styles.staveBox;
+						}
+					}
+				} else {
+					if (state.activeMeasureIndex == item.MeasureNumberXML) {
+						return styles.staveBox;
+					}
+				}
+			};
 		});
 		onMounted(() => {
 			calcNoteData();
 		});
 		return () => (
 			<div class={styles.selectionContainer}>
-				<button onClick={() => gotoCustomNote(4)}>next</button>
-				<button onClick={() => state.osmd.cursor.next()}>next</button>
-				{staveBoxList.value.map((item: any) => {
-					// console.log(state.activeMeasureIndex , item.MeasureNumberXML, '')
+				{selectData.staves.map((item: any) => {
 					return (
 						<>
-							{/* <div class={styles.note} style={item.bbox}></div> */}
-							{item.staveBox && <div class={[styles.position, state.activeMeasureIndex == item.MeasureNumberXML && styles.staveBox]} style={item.staveBox}></div>}
+							{item.staveBox && (
+								<div class={[styles.position, showClass.value(item)]} style={item.staveBox} onClick={() => handleSelection(item)}>
+									{!item.isRestFlag && metronomeData.lineShow && item.MeasureNumberXML === metronomeData.activeMetro?.measureNumberXML && <div class={styles.line} style={{ left: metronomeData.activeMetro.left }}></div>}
+								</div>
+							)}
 						</>
 					);
 				})}
 				{selectData.notes.map((item: any) => {
-					return <div class={[styles.position, styles.note]} style={item.bbox} onClick={() => skipNotePlay(item.index)}></div>;
+					return <div class={[styles.position, state.sectionStatus && styles.disable, styles.note]} style={item.bbox} onClick={() => skipNotePlay(item.index)}></div>;
 				})}
 			</div>
 		);

Some files were not shown because too many files changed in this diff