Przeglądaj źródła

Merge branch 'feature-tianyong' into hotfix-11-11

TIANYONG 7 miesięcy temu
rodzic
commit
ec6b348db0

+ 72 - 0
src/helpers/beatConfig.ts

@@ -0,0 +1,72 @@
+export const unitObj = {
+   "1/1": 1 / 1,
+   "1/2": 1 / 2,
+   "1/4": 1 / 4,
+   "1/8": 1 / 8,
+   "1/16": 1 / 16,
+   "1/2.": (1 / 2) * 1.5,
+   "1/4.": (1 / 4) * 1.5,
+   "1/8.": (1 / 8) * 1.5
+} as Record<string, any>
+
+/**
+ * 速度转换,几分音符的速度转为几分音符的速度
+ */
+export const speedBeatTo = (speedBeat: { unit: string; speed: number }, unit: string) => {
+   return (unitObj[speedBeat.unit] * speedBeat.speed) / unitObj[unit]
+}
+
+/** 谱面速度节拍器转为 约定的节拍器符号 */
+export function beatUnitTo(beatUnit: string, isDot: boolean) {
+   let multiple = "1/4"
+   switch (beatUnit) {
+      case "1024th":
+         multiple = "1/1024"
+         break
+      case "512th":
+         multiple = "1/512"
+         break
+      case "256th":
+         multiple = "1/256"
+         break
+      case "128th":
+         multiple = "1/128"
+         break
+      case "64th":
+         multiple = "1/64"
+         break
+      case "32nd":
+         multiple = "1/32"
+         break
+      case "16th":
+         multiple = "1/16"
+         break
+      case "eighth":
+         multiple = "1/8"
+         break
+      case "quarter":
+         multiple = "1/4"
+         break
+      case "half":
+         multiple = "1/2"
+         break
+      case "whole":
+         multiple = "1/1"
+      default:
+         break
+   }
+   isDot && (multiple += ".")
+   return multiple
+}
+
+/** 几分音符对应的速度图片 */
+export const unitImgs = {
+   "1/1": 'speed1',
+   "1/2": 'speed2',
+   "1/4": 'speed3',
+   "1/8": 'speed4',
+   "1/16": 'spee5',
+   "1/2.": 'speed6',
+   "1/4.": 'speed7',
+   "1/8.": 'speed8',
+} as Record<string, any>

+ 51 - 59
src/helpers/formateMusic.ts

@@ -14,6 +14,8 @@ import {
 	OpenSheetMusicDisplay,
 } from "/osmd-extended/src";
 import { GradualChange, speedInfo } from "./calcSpeed";
+import { beatUnitTo, speedBeatTo } from "/src/helpers/beatConfig"
+
 const browserInfo = browser();
 dayjs.extend(duration);
 
@@ -32,7 +34,7 @@ export const getFixTime = (speed: number) => {
 	const duration: any = getDuration(state.osmd as unknown as OpenSheetMusicDisplay);
 	let numerator = duration.numerator || 0;
 	let denominator = duration.denominator || 4;
-	const beatUnit = duration.beatUnit || "quarter";
+	const beatUnit = "quarter";
 	// if (state.repeatedBeats) {
 	// 	// 音频制作问题仅2拍不重复
 	// 	numerator = numerator === 2 ? 4 : numerator;
@@ -149,9 +151,9 @@ export const getDuration = (osmd?: OpenSheetMusicDisplay): Duration => {
 		const { Duration, TempoInBPM, ActiveTimeSignature, TempoExpressions } = osmd.GraphicSheet.MeasureList[0][0]?.parentSourceMeasure;
 		if (Duration) {
 			let beatUnit = "quarter";
-			for (const item of TempoExpressions) {
-				beatUnit = item.InstantaneousTempo.beatUnit || "quarter";
-			}
+			// for (const item of TempoExpressions) {
+			// 	beatUnit = item.InstantaneousTempo.beatUnit || "quarter";
+			// }
 			const duration = formatDuration(ActiveTimeSignature, Duration) as unknown as FractionDefault;
 			return {
 				...duration,
@@ -238,11 +240,6 @@ export function formatBeatUnit(beatUnit: string) {
 	return multiple;
 }
 
-/** 根据音符单位,速度,几几拍计算正确的时间 */
-export function getTimeByBeatUnit(beatUnit: string, bpm: number, denominator: number) {
-	return (denominator / formatBeatUnit(beatUnit)) * bpm;
-}
-
 export type CustomInfo = {
 	showSpeed: boolean;
 	parsedXML: string;
@@ -682,11 +679,19 @@ export const formatXML = (xml: string, xmlUrl?: string): string => {
 		state.originSpeed = speeds[0] ? speeds[0] : 100;
 		state.speed = state.originSpeed;
 	}
+	// 赋值谱面速度节拍器,没有的时候 以后台传入的为准
+	const metronomeXml = xmlParse.getElementsByTagName('metronome')?.[0]
+	const beatUnit = metronomeXml?.getElementsByTagName('beat-unit')?.[0]?.textContent || ''
+	if(beatUnit){
+		const beatUnitDot = metronomeXml?.getElementsByTagName('beat-unit-dot')?.[0]
+		state.speedBeatUnit = beatUnitTo(beatUnit, !!beatUnitDot)
+	}
 	// 如果谱面和小节都没有打速度,osmd设置的小节速度默认取后台设置的速度
 	if (speeds.length === 0) {
 		;(window as any).baseMeasureSpeed = state.originSpeed
 	} else {
-		state.originAudioPlayRate = speeds[0] / state.originSpeed
+		// 当前谱面的速度转为4分音符速度 因为我们速度比例转为4分音符了
+		state.originAudioPlayRate = speedBeatTo({unit:state.speedBeatUnit, speed:speeds[0]}, "1/4") / state.originSpeed
 	}
 	console.log('是否是变速的曲子:',hasVaryingSpeed,speeds)
 	let repeats: any = [];
@@ -856,6 +861,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 
 	let preNoteMeasureNumber = 0; // 上一个小节的number值
 
+	let currentRealTempo: any = {}; // 当前小节的速度与拍号信息
+
 	const _notes = [] as any[];
 	if (state.gradualTimes) {
 		console.log("后台设置的渐慢小节时间", state.gradual, state.gradualTimes);
@@ -970,13 +977,20 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			}
 			note.maxNoteNum = maxNoteNum;
 			note.trackIndex = minIndex;
+			currentRealTempo = iterator.currentMeasure.tempoExpressions.length ? iterator.currentMeasure.tempoExpressions.find((item: any) => item?.InstantaneousTempo?.isMetronomeMark)?.InstantaneousTempo || currentRealTempo : currentRealTempo;
+			const { beatUnit="quarter", dotted=false, tempoInBpm=state.originSpeed } = currentRealTempo
+			const speedBeatUnit = beatUnitTo(beatUnit, dotted)
 			_notes.push({
 				note,
 				iterator: { ...iterator },
 				currentTime,
 				isDouble,
 				isMutileSubject,
-				measuresTempoInBPM: note?.sourceMeasure?.tempoInBPM
+				// measuresTempoInBPM: note?.sourceMeasure?.tempoInBPM,
+				// 转换成1/4拍的速度
+				measuresTempoInBPM: speedBeatTo({unit: speedBeatUnit || "1/4",speed: tempoInBpm || 0}, `1/4`),
+				speedBeatUnit, // 当前谱面小节的速度对应的是几分音符
+				currentRealTempo
 			});
 		}
 
@@ -994,7 +1008,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 	console.log('变速曲子',hasVaryingSpeed, _notes)
 	let noteIds: any = [];
 	// let voicesBBox: any = null;
-	for (let { note, iterator, currentTime, isDouble, isMutileSubject } of _notes) {
+	for (let { note, iterator, currentTime, isDouble, isMutileSubject, speedBeatUnit, measuresTempoInBPM } of _notes) {
 		if (note) {
 			if (preMeasureNumber != note?.sourceMeasure?.MeasureNumberXML) {
 				si = 0
@@ -1003,14 +1017,14 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				preMeasureNumber = note?.sourceMeasure?.MeasureNumberXML
 				allMeasures.push(note.sourceMeasure);
 			}
-			if (si === 0 && state.isSpecialBookCategory) {
-				for (const expression of (note.sourceMeasure as SourceMeasure)?.TempoExpressions) {
-					if (expression?.InstantaneousTempo?.beatUnit) {
-						// 取最后一个有效的tempo
-						beatUnit = expression.InstantaneousTempo.beatUnit;
-					}
-				}
-			}
+			// if (si === 0 && state.isSpecialBookCategory) {
+			// 	for (const expression of (note.sourceMeasure as SourceMeasure)?.TempoExpressions) {
+			// 		if (expression?.InstantaneousTempo?.beatUnit) {
+			// 			// 取最后一个有效的tempo
+			// 			beatUnit = expression.InstantaneousTempo.beatUnit;
+			// 		}
+			// 	}
+			// }
 			// 判断是否是同一小节
 			if (staveIndex == note.sourceMeasure?.MeasureNumberXML && i !== 0) {
 				staveNoteIndex++
@@ -1102,12 +1116,15 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 
 			let beatSpeed = 0;
 			// 速度不能为0 此处的速度应该是按照设置的速度而不是校准后的速度,否则mp3速度不对
-			if (measureSpeed !== baseSpeed && !hasVaryingSpeed) {
-				beatSpeed = baseSpeed || measureSpeed || 100
-			} else {
-				beatSpeed = (state.isSpecialBookCategory ? measureSpeed : baseSpeed) || 1;
-			}
+			// if (measureSpeed !== baseSpeed && !hasVaryingSpeed) {
+			// 	beatSpeed = baseSpeed || measureSpeed || 100
+			// } else {
+			// 	beatSpeed = (state.isSpecialBookCategory ? measureSpeed : baseSpeed) || 1;
+			// }
+			// 计算音符时值,使用转换成1/4的速度计算
+			beatSpeed = measuresTempoInBPM;
 			// let beatSpeed = measureSpeed || baseSpeed
+			beatSpeed = beatSpeed / state.originAudioPlayRate;
 			// 如果有节拍器,需要将节拍器的时间算出来
 			if (i === 0) {
 				if(state.isOpenMetronome){
@@ -1118,9 +1135,9 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				xmlMp3BeatFixTime = getFixTime(beatSpeed)
 				// console.log("fixtime:", fixtime, '速度:', beatSpeed, "state.isSpecialBookCategory:", state.isSpecialBookCategory, 'state.isOpenMetronome:', state.isOpenMetronome);
 			}
-			// console.log(getTimeByBeatUnit(beatUnit, measureSpeed, iterator.currentMeasure.activeTimeSignature.Denominator))
 			let gradualLength = 0;
-			let speed = (state.isSpecialBookCategory ? measureSpeed : baseSpeed) || 1;
+			// let speed = (state.isSpecialBookCategory ? measureSpeed : baseSpeed) || 1;
+			let speed = measureSpeed ? measureSpeed : baseSpeed;
 			gradualChange = iterator.currentMeasure.speedInfo || gradualChange;
 			gradualSpeed = osmd.Sheet.SoundTempos?.get(note.sourceMeasure.measureListIndex) || gradualSpeed;
 			if (!gradualSpeed || gradualSpeed.length < 2) {
@@ -1158,34 +1175,6 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 						}
 					}
 				}
-			} else if (state.appName === "GYM" && gradualChange && gradualSpeed && (gradualChange.startXmlNoteIndex === si || gradualChangeIndex > 0)) {
-				const startSpeed = gradualSpeed[0] - (gradualSpeed[1] - gradualSpeed[0]);
-				const { resetXmlNoteIndex, endXmlNoteIndex } = gradualChange;
-				const noteDiff = endXmlNoteIndex;
-				let stepSpeed = (gradualSpeed[gradualSpeed.length - 1] - startSpeed) / noteDiff;
-				stepSpeed = note.DotsXml ? stepSpeed / 1.5 : stepSpeed;
-				if (gradualChangeIndex < noteDiff) {
-					const tempSpeed = Math.ceil(speed + stepSpeed * gradualChangeIndex);
-					let tmpSpeed = getTimeByBeatUnit(beatUnit, tempSpeed, iterator.currentMeasure.activeTimeSignature.Denominator);
-					const maxLength = (wholeValue + numerator / denominator) * vDenominator * (60 / tmpSpeed);
-					// speed += stepSpeeds.reduce((a, b) => a + b, 0)
-					speed += Math.ceil(stepSpeed * (gradualChangeIndex + 1));
-					tmpSpeed = getTimeByBeatUnit(beatUnit, speed, iterator.currentMeasure.activeTimeSignature.Denominator);
-					const minLength = (wholeValue + numerator / denominator) * vDenominator * (60 / tmpSpeed);
-					gradualLength = (maxLength + minLength) / 2;
-				} else if (resetXmlNoteIndex > gradualChangeIndex) {
-					speed = allNotes[i - 1]?.speed;
-				}
-				beatSpeed =
-					(state.isSpecialBookCategory ? getTimeByBeatUnit(beatUnit, speed, iterator.currentMeasure.activeTimeSignature.Denominator) : baseSpeed) || 1;
-				const isEnd = !(gradualChangeIndex < noteDiff) && !(resetXmlNoteIndex > gradualChangeIndex);
-				gradualChangeIndex++;
-				if (isEnd) {
-					gradualChangeIndex = 0;
-					gradualChange = undefined;
-					gradualSpeed = undefined;
-					stepSpeeds = [];
-				}
 			}
 			const _noteLength = NoteRealValue;
 			// 当前音符的持续时长,当前音符的RealValue值*拍数*(60/后台设置的基准速度)
@@ -1232,9 +1221,11 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				const staffEntries = note.sourceMeasure.verticalMeasureList?.[0]?.staffEntries || [];
 				//计算第一个小节里面的音符时值是否等于整个小节的时值
 				staffEntries.forEach((_a: any) => {
-					if (_a?.sourceStaffEntry?.voiceEntries?.[0]?.notes?.[0]?.length?.realValue) {
-						_firstMeasureRealValue += _a.sourceStaffEntry.voiceEntries[0].notes[0].length.realValue;
-					}
+					// 需要过滤掉倚音音符
+					const matchNote = _a?.sourceStaffEntry?.voiceEntries?.length > 1 ? _a?.sourceStaffEntry?.voiceEntries.find((item: any) => !item.isGrace) : _a?.sourceStaffEntry?.voiceEntries?.[0]
+					if (matchNote?.notes?.[0]?.length?.realValue) {
+						_firstMeasureRealValue += matchNote.notes[0].length.realValue;
+					}					
 				});
 				if (_firstMeasureRealValue < vRealValue) {
 					// console.log(_firstMeasureRealValue, vRealValue)
@@ -1413,7 +1404,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				firstVerticalMeasure: activeVerticalMeasureList[0],
 				noteLength: 1,
 				//osdmContext: osmd,
-				speedbeatUnit: beatUnit,
+				// speedbeatUnit: beatUnit,
+				speedBeatUnit, // 当前谱面小节的速度对应的是几分音符
 				multipleRestMeasures: multipleRestMeasures, // 当前合并小节的索引,从1开始到当前的totalMultipleRestMeasures结束,
 				totalMultipleRestMeasures, // 当前小节总的合并小节数
 				measureSpeed,  // 小节速度

+ 123 - 121
src/helpers/metronome.ts

@@ -42,6 +42,7 @@ export const metronomeData = reactive({
 	cursorTips: '' as string, // 光标模式提示文字
 	followAudioIndex: 1, // 当前的拍数
 	totalNumerator: 2, // 总拍数
+	firstBeatTypeArr:[] as number[] // 第一小节的节拍
 });
 
 watch(
@@ -323,6 +324,11 @@ class Metronome {
 			// console.log("🚀 ~ measureNumberXML", measureNumberXML, note)
 			// console.log("🚀 ~ measureNumberXML", note)
 			const measureListIndex = measureNumberXML - 1;
+			// 当渐快渐慢的时候  不播节拍器
+			if(isWithinRange(state.gradual, measureListIndex)){
+				xmlNumber = measureNumberXML;
+				continue
+			}
 			if (measureNumberXML > -1) {
 				if (measureNumberXML != xmlNumber) {
 					// 弱起的时候 根据音符结尾时间减去音符开头时间,得到的不是正常小节的时间,然后平均分配节拍之后,当前节拍间隔会非常短 这里弱起取整个小节的时间
@@ -367,7 +373,9 @@ class Metronome {
 					const m = {
 						measureNumberXML: measureNumberXML,
 						measureNumberIndex: measureListIndex,
+						CompoundTempo: note?.noteElement?.sourceMeasure?.CompoundTempo || "",
 						numerator: note?.noteElement?.sourceMeasure?.ActiveTimeSignature?.numerator || 0,
+						denominator: note?.noteElement?.sourceMeasure?.ActiveTimeSignature?.denominator || 0,
 						start: startTime,
 						end: noteEndTime,
 						time: noteEndTime - startTime,
@@ -377,48 +385,15 @@ class Metronome {
 						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;
-						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 {
-						/**
-						 * bug:#9877
-						 * 多分轨合并显示,不同分轨的音符数量可能不同
-						 */
-						let measureArr = note.measures;
-						if (state.isCombineRender) {
-							measureArr = measureArr.filter((item: any) => item.MeasureNumberXML === m.measureNumberXML)
-						}
-						m.stepList = calculateMetroStep(measureArr, m);
+					/**
+					 * bug:#9877
+					 * 多分轨合并显示,不同分轨的音符数量可能不同
+					*/
+					let measureArr = note.measures;
+					if (state.isCombineRender) {
+						measureArr = measureArr.filter((item: any) => item.MeasureNumberXML === m.measureNumberXML)
 					}
+					m.stepList = calculateMetroStep(measureArr, m);
 					measures.push(m);
 					xmlNumber = measureNumberXML;
 				}
@@ -435,34 +410,60 @@ class Metronome {
 				measureNumberXML: item.measureNumberXML
 			}
 		}))
-		// 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);
+				// 87拍和45拍要根据小节返回的CompoundTempo特殊处理
+				const beatTypeArr = getBeatTypeArr(measure.numerator, measure.denominator, measure.CompoundTempo)
+				const CompoundTempoArr = beatTypeArr.map((beatType:number) => {
+					return Math.abs(beatType*measure.numerator)
+				})
+				if(i===0){
+					metronomeData.firstBeatTypeArr = beatTypeArr
+				}
 				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;
+				// 根据有几个拍子,划分成几份
+				const widthStep = 100 / (beatTypeArr.length+1);
+				// 当前拍子的组合数(2+3+2,3+2)中的数字
+				let beatNum = 0;
+				// if (measure.measureNumberXML == 98) {
+				// 	debugger
+				// }
+				for (let j = 0; j < beatTypeArr.length; j++) {
+					// 累加
+					const beatMuit = Array(j).fill("").reduce((num:number,v:any,i:number) => {
+						return num += Math.abs(beatTypeArr[i])
+					}, 0) || 0
+					const time = measure.time * beatMuit + measure.start;
 					metroList.push(time);
 					let left = "";
-					if (measure.stepList[j]) {
-						left = measure.stepList[j] + "px";
+					// 当前拍子数对应的节拍位置索引
+					let currentIdx = 0;
+					if (j == 0) {
+						currentIdx = 0
+					} else {
+						beatNum += CompoundTempoArr[j-1];
+						currentIdx = beatNum
+					}
+					// 如果是87拍,并且是3+2+2的组合,第二拍的节拍指针需要定位到第四个音符的位置
+					// if (measure.numerator === 7 && measure.denominator === 8 && measure.CompoundTempo === '3+2+2' && j === 1) {
+					// 	currentIdx += 1;
+					// }
+					if (measure.stepList[currentIdx]) {
+						left = measure.stepList[currentIdx] + "px";
 					} else {
 						const preLeft = measure.stepList[j - 1];
-						left = !preLeft ? `${widthStep}%` : preLeft.toString().indexOf("%") > -1 ? `${preLeft} + ${widthStep}%` : `${preLeft}px + ${widthStep}%`;
+						left = !preLeft || preLeft.toString().indexOf("%") > -1 ? `${widthStep*(j+1)}%` : `${preLeft}px + ${widthStep}%`;
 						measure.stepList[j] = left;
-					}
+					}					
 					metroMeasure[i].push({
+						isTick: beatTypeArr[j] < 0,
 						index: j,
 						time,
-						// left: (measure.stepList[j] ? measure.stepList[j] + 'px' : (j + 1) * widthStep + '%'),
 						left: left?.indexOf("%") > -1 ? `calc(${left})` : left,
 						measureNumberXML: measure.measureNumberXML,
 						isRestFlag: measure.isRestFlag,
+						stepList: measure.stepList
 					});
 				}
 			}
@@ -478,6 +479,64 @@ class Metronome {
 	}
 }
 
+/** 获取节拍类型数组 */
+export function getBeatTypeArr(numerator?:number, denominator?:number, CompoundTempo?:string){
+	const speedBeatUnit = state.speedBeatUnit
+	const Numerator = numerator || state.osmd?.Sheet?.SheetPlaybackSetting?.Rhythm?.Numerator || 4
+	const Denominator = denominator || state.osmd?.Sheet?.SheetPlaybackSetting?.Rhythm?.Denominator || 4
+	let loopArr = []
+	// 规则 负数代表重声,正数代表轻声
+	switch (`${Numerator}/${Denominator}`) {
+		case "2/2":
+			loopArr = [-1/2, 1/2]
+			break;		
+		case "3/2":
+			loopArr = [-1/3, 1/3, 1/3]
+			break;
+		case "5/4":
+			//5/4拍根据谱面的CompoundTempo来特殊处理
+			if(CompoundTempo==="2+3"){
+				loopArr = [-1/5, 1/5, -1/5, 1/5, 1/5]
+			}else{
+				loopArr = [-1/5, 1/5, 1/5, -1/5, 1/5]
+			}
+			break;
+		case "3/8":
+			// 3/8拍 速度为浮点4分音符时候特殊处理
+			if(speedBeatUnit==="1/4."){
+				loopArr = [-1/1]
+			}else{
+				loopArr = [-1/3, 1/3, 1/3]
+			}
+			break;
+		case "6/8":
+			loopArr = [-1/2, 1/2]
+			break;	
+		case "7/8":
+			//7/8拍根据谱面的CompoundTempo来特殊处理
+			if(CompoundTempo==="2+2+3"){
+				loopArr = [-2/7, 2/7, 3/7]
+			}else if(CompoundTempo==="2+3+2"){
+				loopArr = [-2/7, 3/7, 2/7]
+			}else{
+				loopArr = [-3/7, 2/7, 2/7]
+			}
+			break;	
+		case "9/8":
+			loopArr = [-3/9, 3/9, 3/9]
+				break;				
+		default:
+			loopArr.push(-1/Numerator);
+			for (let i = 1; i < Numerator; i++) {
+				loopArr.push(1/Numerator);
+			}
+			break;
+	}
+	// console.log(loopArr, "loopArr")
+	return loopArr
+}
+
+
 // 计算拍子的时值
 function calculateMetroStep(arr: any[], m: any): number[] {
 	const measureLength = arr.reduce((total: number, item: any) => {
@@ -604,72 +663,6 @@ function calculateMetroStep(arr: any[], m: any): number[] {
 	// 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]?.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 [];
-	}
-	// 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)
-	return stepList;
-}
 
 // 延迟兼容处理
 function setCurrentTime(time: number) {
@@ -700,5 +693,14 @@ function hideCursorTip() {
 		}, 2000);
 	}
 }
-
+function isWithinRange(ranges:any[], index:number) {
+	for (const range of ranges) {
+		const start = range[0].measureIndex;
+		const end = range[1].measureIndex;
+		if (index >= start && index < end) {
+			return true;
+		}
+	}
+	return false;
+}
 export default Metronome;

+ 2 - 2
src/page-instrument/custom-plugins/recording-time/index.tsx

@@ -16,7 +16,7 @@ const handleRecord = () => {
 	if (total < 0) total = 0;
 	const totalTime = total / 1000;
 	const query: any = getQuery();
-
+	
 	const body: any = {
 		clientType: storeData.user.clientType,
 		musicSheetId: state.examSongId,
@@ -30,7 +30,7 @@ const handleRecord = () => {
 	// 如果是作业模式,需要添加作业id
 	if (query.workRecord || query.evaluatingRecord) {
 		body.lessonDetailId = query.workRecord || query.evaluatingRecord
-	}	
+	}
 	api_musicPracticeRecordSave(body);
 };
 

+ 1 - 1
src/page-instrument/evaluat-model/evaluat-result/index.tsx

@@ -71,7 +71,7 @@ export default defineComponent({
       // 如果是评测作业模式,需要添加作业id
       if (query.evaluatingRecord) {
         body.lessonDetailId = query.evaluatingRecord
-      }      
+      }
       data.saveLoading = true;
       const res = await api_musicPracticeRecordSave(body);
       if (res?.code === 200) {

+ 1 - 1
src/page-instrument/evaluat-model/index.tsx

@@ -366,7 +366,7 @@ export default defineComponent({
         // 再来一次,需要手动取消评测,不生成评测记录,不显示评测结果弹窗
         evaluatingData.oneselfCancleEvaluating = true;
         // handleCancelEvaluat();
-        handleEndEvaluat(true, 'selfCancel');
+        handleEndEvaluat(false, 'selfCancel');
         // evaluatingData.isBeginMask = true;
         evaluatingData.evaluatings = {};
         state.playState = "paused";

BIN
src/page-instrument/header-top/image/speed1.png


BIN
src/page-instrument/header-top/image/speed2.png


BIN
src/page-instrument/header-top/image/speed3.png


BIN
src/page-instrument/header-top/image/speed4.png


BIN
src/page-instrument/header-top/image/speed5.png


BIN
src/page-instrument/header-top/image/speed6.png


BIN
src/page-instrument/header-top/image/speed7.png


BIN
src/page-instrument/header-top/image/speed8.png


+ 1 - 1
src/page-instrument/header-top/index.tsx

@@ -857,7 +857,7 @@ export default defineComponent({
                   <img style={{ display: !metronomeData.disable ? "block" : "none" }} class={styles.iconBtn} src={headImg("tickoff.png")} />
                   <span style={{ whiteSpace: "nowrap" }}>节拍</span>
                   <div class={styles.speedCon}>
-                    <img src={headImg("speed.png")} />
+                    <img src={headImg(`${state.speedIcon}.png`)} />
                     <div>{Math.floor(state.speed)}</div>
                   </div>
                 </div>

+ 13 - 7
src/page-instrument/header-top/settting/index.module.less

@@ -151,8 +151,10 @@
                 }
                 .radioBox{
                     display: flex;
+                    justify-content:flex-end;
+                    flex-wrap: wrap;
                     > div{
-                        width: 60px;
+                        width: 48px;
                         height: 25px;
                         font-weight: 600;
                         font-size: 14px;
@@ -160,23 +162,27 @@
                         text-align: center;
                         line-height: 25px;
                         margin-right: 8px;
+                        margin-bottom: 8px;
                         cursor: pointer;
                         background: #F2F2F2;
                         border-radius: 4px;
-                        &:last-child{
+                        &:nth-child(4),&:nth-child(8) {
                             margin-right: 0;
                         }
+                        &:nth-child(n+5) {
+                            margin-bottom: 0;
+                        }
                         &.active{
                             color: #ffffff;
                             background: linear-gradient( 135deg, #04C8BB 0%, #60E0C5 100%);
                         }
                     }
                 }
-                .speBox {
-                    >div {
-                        width: 48px;
-                    }
-                }                
+                // .speBox {
+                //     >div {
+                //         width: 48px;
+                //     }
+                // }                
                 .frequency{
                     display: flex;
                     align-items: center;

+ 1 - 1
src/page-instrument/header-top/settting/index.tsx

@@ -26,7 +26,7 @@ export default defineComponent({
 			screenModelShow: false, // 投屏帮助
 			recommendationShow: false, // 建议
 		});
-        const zoomList = [{name:'默认',value: 0.8},{name:'1.25x',value: 1.25},{name:'1.5x',value: 1.5},{name:'1.75x',value: 1.75}] 
+        const zoomList = [{name:'0.5x',value: 0.5},{name:'0.75x',value: 0.65},{name:'1x',value: 0.8},{name:'1.25x',value: 1.25},{name:'1.5x',value: 1.5},{name:'1.75x',value: 1.75},{name:'2x',value: 2},{name:'2.25x',value: 2.25}] 
 		const parentClassName = "recommenBoxClass_drag";
 		const userId = storeData.user?.id ? String(storeData.user?.id) : "";
 		const positionInfo =

+ 8 - 3
src/page-instrument/header-top/speed/index.module.less

@@ -123,18 +123,23 @@
             }
             .speedSel{
                 margin-top: 20px;
-                padding-bottom: 18px;
+                padding-bottom: 8px;
                 display: flex;
                 justify-content: space-between;
+                flex-wrap: wrap;
                 & > div{
-                    padding: 6px 13px;
+                    width: 42px;
+                    height: 24px;
+                    line-height: 24px;
+                    text-align: center;
                     background: #F6F6F6;
                     border-radius: 14px;
                     font-weight: 400;
                     font-size: 13px;
                     color: rgba(0,0,0,0.6);
-                    line-height: 1;
                     cursor: pointer;
+                    margin-bottom: 10px;
+                    margin-right: 3px;
                     &:active{
                         background: #B3EDE9;
                         color: #131415;

+ 6 - 20
src/page-instrument/header-top/speed/index.tsx

@@ -35,23 +35,11 @@ export default defineComponent({
 			() => speed.value,
 			() => {
 				// handleSetSpeed(speed.value);
-				//console.log('速度123',speed.value)
-				state.speed = Math.floor(speed.value);
+				// state.speed = Math.floor(speed.value);
+				state.speed = speed.value
 				// handleSetSpeed(speed.value);
 				if (state.playState === 'paused') {
-					let currentItem: any;
-					if (state.section.length === 2 && state.sectionStatus && state.times[state.activeNoteIndex].MeasureNumberXML === state.sectionFirst.MeasureNumberXML) {
-						currentItem = state.section[0]
-					}
-					if (state.section.length === 2 && state.sectionStatus && state.times[state.activeNoteIndex].MeasureNumberXML !== state.sectionFirst.MeasureNumberXML) {
-						currentItem = state.times[state.activeNoteIndex]
-					}
-					// if (state.section.length === 2 && state.sectionStatus && state.times[state.activeNoteIndex].MeasureNumberXML !== state.sectionFirst.MeasureNumberXML) {
-					// 	currentItem = state.times[state.activeNoteIndex]
-					// } else {
-					// 	currentItem = (state.sectionStatus && state.section.length === 2) ? state.section[0] : state.times[state.activeNoteIndex];
-					// }
-					
+					const currentItem: any = (state.sectionStatus && state.section.length === 2) ? state.sectionFirst || state.section[0] : state.times[state.activeNoteIndex];
 					state.basePlayRate = currentItem?.measureSpeed ? state.speed / currentItem.measureSpeed : state.speed / state.originSpeed;
 				}
 			}
@@ -145,11 +133,9 @@ export default defineComponent({
 						</div>
 						<div class={[styles.speedSel, (workData.trainingType === "PRACTICE" || workData.trainingType === "EVALUATION") && styles.disableSpend]}>
 							<div onClick={resetCurrentSpeed}>原速</div>
-							<div onClick={()=>{ speed.value = 70 }}>70</div>
-							<div onClick={()=>{ speed.value = 80 }}>80</div>
-							<div onClick={()=>{ speed.value = 90 }}>90</div>
-							<div onClick={()=>{ speed.value = 100 }}>100</div>
-							<div onClick={()=>{ speed.value = 110 }}>110</div>
+							{[60,70,80,90,100,110,120,130,140,150,160].map((item) => (
+								<div onClick={()=>{ speed.value = item }}>{item}</div>
+							))}
 						</div>
 						{
 							state.isMixBeat && state.modeType !== "evaluating" &&

+ 5 - 2
src/page-instrument/view-detail/index.tsx

@@ -246,7 +246,9 @@ export default defineComponent({
       try {
         metronomeData.metro = new Metronome();
         metronomeData.metro.init(state.times);
-      } catch (error) {}
+      } catch (error) {
+        console.log(error, "err")
+      }
 
       // 需要向外面(iframe)派发计时器数据的时候触发
       if (query.isbeatTimes) {
@@ -300,7 +302,7 @@ export default defineComponent({
        * 设置节拍器,跟练需要播放系统节拍器,所以不需要判断needTick状态
        */
       // if (state.needTick) {
-      handleInitTick(osmd?.Sheet?.SheetPlaybackSetting?.Rhythm?.Numerator || 4, osmd?.Sheet?.SheetPlaybackSetting?.Rhythm?.denominator);
+      handleInitTick();
       // }
       // api_cloudLoading();
       // state.playBtnDirection = query.imagePos === 'left' ? 'left' : 'right';
@@ -326,6 +328,7 @@ export default defineComponent({
       try{
         handleRendered(osmd)
       }catch(err:any){
+        console.log(err, "err")
         // 需要向外面(iframe)派发计时器数据的时候触发
         if(query.isbeatTimes){
           console.log("webApi_beatTimes",err)

+ 1 - 0
src/page-instrument/view-evaluat-report/index.tsx

@@ -267,6 +267,7 @@ export default defineComponent({
     const setViewColor = () => {
       clearViewColor();
       const notes = filterNotes();
+      console.log('评测报告111')
       // console.log(1111,notes)
       for (const note of notes) {
         const idx = note.musicalNotesIndex !== undefined ? note.musicalNotesIndex : note.index;

+ 20 - 20
src/state.ts

@@ -23,6 +23,7 @@ import { headTopData } from "/src/page-instrument/header-top/index";
 import { api_lessonTrainingTrainingStudentDetail } from "/src/page-instrument/api"
 import { undoData, moveData } from "/src/view/plugins/move-music-score"
 import { HANDLE_WORK_ADD } from "/src/page-instrument/custom-plugins/work-index";
+import { speedBeatTo, unitImgs } from "/src/helpers/beatConfig"
 
 const query: any = getQuery();
 
@@ -360,6 +361,8 @@ const state = reactive({
   bizMusicCategoryId: 0,
   /** 资源类型: mp3 | midi */
   playMode: "MP3" as "MP3" | "MIDI",
+  /** 谱面的速度节拍 */
+  speedBeatUnit: "1/4",
   /** 设置的速度 */
   speed: 0,
   /** 曲谱音频正常的速度 */
@@ -597,6 +600,8 @@ const state = reactive({
   isAutoRePlay: false,
   /** 跟练,是否收到了录音的回调 */
   hasFollowResult: false,  
+  /** 右上角速度图标,根据当前小节的速度是几分音符的动态变化 */
+  speedIcon: 'speed3', // 默认取1/4拍的图片
 });
 const browserInfo = browser();
 let offset_duration = 0;
@@ -681,13 +686,9 @@ export const onEnded = () => {
 const dynamicShowPlaySpeed = (index: number, isPlaying?: boolean) => {
   //if (!headerColumnHide.value) {
     const item: any = state.times[index];
-    if (state.sectionFirst && state.section.length === 2 && item.MeasureNumberXML === state.sectionFirst.MeasureNumberXML) {
-      state.speed = Math.floor(state.section[0].measureSpeed * state.basePlayRate) || state.speed
-      return;
-    }
     if (item && item.measureSpeed ) {
       // console.log('速度1',item.measureSpeed)
-      const newSpeed = Math.floor(state.basePlayRate * item.measureSpeed)
+      const newSpeed = state.basePlayRate * item.measureSpeed
       if (state.speed !== newSpeed) {
         state.speed = newSpeed;
       }
@@ -703,14 +704,11 @@ export const initSetPlayRate = () => {
     state.isAutoRePlay = false
     return
   }
-  let item: any = (state.sectionStatus && state.section.length === 2) ? state.section[0] : state.times[state.activeNoteIndex];
+  // let item: any = (state.sectionStatus && state.section.length === 2) ? state.sectionFirst || state.section[0] : state.times[state.activeNoteIndex];
+  let item: any = state.times[state.activeNoteIndex];
   console.log('播放状态',state.playState)
-  // 如果是选段,并且不是选段内的第一小节,取当前小节的速度
-  if (state.sectionStatus && state.section.length === 2 && state.times[state.activeNoteIndex].MeasureNumberXML !== state.section[0].MeasureNumberXML && state.times[state.activeNoteIndex].MeasureNumberXML !== state.sectionFirst?.MeasureNumberXML) {
-    item = state.times[state.activeNoteIndex];
-  }
   if (item && item.measureSpeed) {
-    const ratio = state.speed / Math.floor(item.measureSpeed)
+    const ratio = state.speed / item.measureSpeed
     // state.audiosInstance?.setSpeed(ratio)
     state.basePlayRate = ratio || 1;
     console.log('播放倍率',state.basePlayRate)
@@ -721,10 +719,6 @@ export const initSetPlayRate = () => {
 export const resetBaseRate = (idx?: number) => {
   const index = idx ? idx : 0;
   let currentItem: any = state.times[index];
-  // 如果是在预备小节点击原速,需要重置为选段第一小节的速度
-  if (state.section.length === 2 && state.sectionFirst && state.sectionFirst.MeasureNumberXML === currentItem.MeasureNumberXML) {
-    currentItem = state.section[0];
-  }
   const currentSpeed = currentItem?.measureSpeed ? currentItem.measureSpeed : state.originSpeed;
   // console.log('速度2',currentSpeed)
   state.speed = currentSpeed
@@ -832,7 +826,8 @@ export const skipNotePlay = async (itemIndex: number, isStart = false, handType?
   if (item) {
     // 非选段模式,点击音符,动态设置右下角的速度
     if (item.measureSpeed && state.section.length < 2) {
-      state.speed = Math.floor(state.basePlayRate * item.measureSpeed)
+      // console.log('速度3')
+      state.speed = state.basePlayRate * item.measureSpeed
     }
     setAudioCurrentTime(itemTime, itemIndex);
     // 一行谱,点击音符,或者播放完成,需要跳转音符位置
@@ -1353,7 +1348,7 @@ export const hanldeDirectSelection = (list: any[]) => {
   setTimeout(() => {
     state.section = formateSelectMearure(list);
     // 选段完成后,需要根据预报小节的速度,设置右下角显示的速度
-    const currentItem: any = (state.sectionStatus && state.section.length === 2) ? state.section[0] : state.times[state.activeNoteIndex];
+    const currentItem: any = (state.sectionStatus && state.section.length === 2) ? state.sectionFirst || state.section[0] : state.times[state.activeNoteIndex];
     if (currentItem.measureSpeed && query.workRecord === undefined && query.evaluatingRecord === undefined) {
       handleSetSpeed(currentItem.measureSpeed);
     }
@@ -1790,9 +1785,10 @@ const setState = (data: any, index: number) => {
   state.musicSheetCategoriesId = data.musicCategoryId;
   state.bizMusicCategoryId = data.bizMusicCategoryId
   state.playMode = data.playMode === "MP3" ? "MP3" : "MIDI";
-  state.originSpeed = state.speed = parseFloat(data.playSpeed) || 0;
-  // state.originSpeed = state.speed = data.playSpeed;
-  // state.playIngSpeed = data.playSpeed;
+  // 设置速度节拍
+  state.speedBeatUnit = data.speedBeatUnit || "1/4"
+  // 这里把后台设置的速度 转换为1/4拍的速度 
+  state.originSpeed = state.speed = speedBeatTo({unit: data.speedBeatUnit || "1/4",speed: parseFloat(data.playSpeed) || 0}, `1/4`);
   const track = data.code || data.track;
   state.track = track ? track.replace(/ /g, "").toLocaleLowerCase() : "";
   // 能否评测,根据当前声轨有无伴奏判断
@@ -2152,6 +2148,10 @@ function getNeedReduceMultipleRestNum(currMeasureIndex: number) {
 watch(
   () => state.activeMeasureIndex,
   () => {
+    // 监听音符小节的变化,取对应的小节速度图片
+    const currentNote = state.times[state.activeNoteIndex]
+    state.speedIcon = unitImgs[currentNote.speedBeatUnit]
+    
     // 需要减去的合并小节数
     // const needReduceMultipleRestNum = getNeedReduceMultipleRestNum(state.activeMeasureIndex)
     // const matchMeasureNum = state.activeMeasureIndex - needReduceMultipleRestNum - 1

+ 3 - 2
src/utils/crunker.ts

@@ -78,9 +78,10 @@ export default class Crunker {
       const output = this._context.createBuffer(this._maxNumberOfChannels(buffers), this._sampleRate * this._maxDuration(buffers), this._sampleRate)
       buffers.forEach((buffer, index) => {
          const offsetNum = Math.round(times[index] * this._sampleRate) //时间偏差
-         for (let channelNumber = 0; channelNumber < buffer.numberOfChannels; channelNumber++) {
+         for (let channelNumber = 0; channelNumber < output.numberOfChannels; channelNumber++) {
             const outputData = output.getChannelData(channelNumber)
-            const bufferData = buffer.getChannelData(channelNumber)
+            // buffers 有可能是单声道,当单声道的时候 取第一个声道的值
+            const bufferData = buffer.getChannelData(buffer.numberOfChannels < 2 ? 0 : channelNumber)
             for (let i = bufferData.length - 1; i >= 0; i--) {
                // 当合并大于1或者小于-1的时候可能会爆音  所以这里取最大值和最小值
                const combinedValue = outputData[i + offsetNum] + bufferData[i]

+ 1 - 1
src/view/audio-list/index.tsx

@@ -359,7 +359,7 @@ export default defineComponent({
 				const beatsBgTime:number[] = []
 				metronomeData.metroMeasure.map(measures=>{
 					measures.map((item:any)=>{
-						beats.push(item.index===0?tickBuff!:tockBuff!)
+						beats.push(item.isTick?tickBuff!:tockBuff!)
 						beatsTime.push(item.time + silenceDuration) // xml 计算的时候 加上空白的时间
 						beatsBgTime.push(item.time + silenceBgDuration) // xml 计算的时候 加上空白的时间 没有背景不赋值
 					})

+ 4 - 1
src/view/music-score/index.module.less

@@ -82,7 +82,10 @@
 .inGradualRange{
    :global{
         #cursorImg-0{
-            // opacity: 0 !important;
+            opacity: 0 !important;
+        }
+        .node-dot {
+            opacity: 0 !important;
         }
    } 
 }

+ 58 - 6
src/view/selection/index.module.less

@@ -353,33 +353,85 @@
 
 .middleZoom {
     .line {
-        height: 70PX;
+        height: 75PX;
     }
     :global {
         .node-dot::before{
-            height: 70PX;
+            height: 75PX;
         }
     }
 }
 
 .bigZoom {
     .line {
-        height: 80PX;
+        height: 90PX;
     }
     :global {
         .node-dot::before{
-            height: 80PX;
+            height: 90PX;
         }
     }
 }
 
 .largeZoom {
     .line {
-        height: 90PX;
+        height: 105PX;
     }
     :global {
         .node-dot::before{
-            height: 90PX;
+            height: 105PX;
+        }
+    }
+}
+
+.largeZoom2 {
+    .line {
+        height: 120PX;
+        width: 3PX;
+    }
+    :global {
+        .node-dot::before{
+            height: 120PX;
+            width: 3PX;
+        }
+    }
+}
+
+.largeZoom3 {
+    .line {
+        height: 140PX;
+        width: 3PX;
+    }
+    :global {
+        .node-dot::before{
+            height: 140PX;
+            width: 3PX;
+        }
+    }
+}
+
+.smallZoom {
+    .line {
+        height: 45PX;
+        width: 1PX;
+    }
+    :global {
+        .node-dot::before{
+            height: 45PX;
+            width: 1PX;
+        }
+    }
+}
+
+.litteZoom {
+    .line {
+        height: 35PX;
+        width: 1PX;
+    }
+    :global {
+        .node-dot::before{
+            height: 35PX;
+            width: 1PX;
         }
     }
 }

+ 2 - 1
src/view/selection/index.tsx

@@ -275,7 +275,8 @@ export default defineComponent({
 					class={[
 						styles.selectionContainer,
 						isPad && styles.isPad,
-						state.zoom == 1.25 ? styles.middleZoom : state.zoom == 1.5 ? styles.bigZoom : state.zoom == 1.75 ? styles.largeZoom : ''
+						state.zoom == 1.25 ? styles.middleZoom : state.zoom == 1.5 ? styles.bigZoom : state.zoom == 1.75 ? styles.largeZoom : state.zoom == 2 ? styles.largeZoom2 : state.zoom == 2.25 ? styles.largeZoom3 : 
+						state.zoom == 0.65 ? styles.smallZoom : state.zoom == 0.5 ? styles.litteZoom : ''
 					]}
 					onClick={(e: Event) => e.stopPropagation()}
 				>

+ 16 - 15
src/view/tick/index.tsx

@@ -7,14 +7,14 @@ 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,
-	denominator: undefined as undefined | number,
 	reduceLen: 0,
 	tickEnd: false,
 	/** 节拍器时间 */
-	beatLengthInMilliseconds: 0,
+	beatLengthInMilliseconds: [] as number[],
 	index: 0,
 	show: false
 });
@@ -70,7 +70,7 @@ const handlePlay = (i: number, source: any | null) => {
 		} else {
 			_time=setTimeout(() => {
 				tickPlayCb(i, resolve, source);
-			}, tickData.beatLengthInMilliseconds);
+			}, Math.abs(tickData.beatLengthInMilliseconds[i-1])*1000/state.basePlayRate);
 		}
 	});
 };
@@ -95,17 +95,19 @@ const createAudio = (src: string): Promise<HTMLAudioElement | null> => {
 };
 
 /** 设置节拍器
- * @param beat 节拍数
- * @param denominator 节拍器分母
  */
-export const handleInitTick = (beat: number, denominator?: number) => {
-	tickData.len = beat;
-	tickData.denominator = denominator;
-	// 节拍器的个数除以2 直到小于等于4为止 
-	while (beat > 4 && beat % 2 === 0) {
-        beat = beat / 2;
-    }
-	tickData.reduceLen = beat
+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
 };
 
 /** 开始节拍器 */
@@ -114,12 +116,11 @@ export const handleStartTick = async () => {
 	tickData.show = true;
 	tickData.tickEnd = false;
 	tickData.index = 0;
-	tickData.beatLengthInMilliseconds = tickData.denominator ? 4 / tickData.denominator * (60 / state.speed) * 1000 : (60 / state.speed) * 1000;
 	for(let i = 0; i <= useLen.value; i++){
 		// 提前结束, 直接放回false
 		if (tickData.tickEnd) return false;
 		// Audio 标签播放音频
-		const source = i === 0 ? audioData.tick : i === useLen.value ? null : audioData.tock;
+		const source = tickData.beatLengthInMilliseconds[i] < 0 ? audioData.tick : i === useLen.value ? null : audioData.tock;
 		await handlePlay(i, source)
 	}
 	tickData.show = false;

+ 1 - 1
vite.config.ts

@@ -52,7 +52,7 @@ export default defineConfig({
     // https: true,
     proxy: {
       "^/instrument/.*": {
-        target: "https://test.gym.lexiaoya.cn",
+        target: "https://dev.gym.lexiaoya.cn",
         changeOrigin: true,
         rewrite: (path) => path.replace(/^\/instrument/, ""),
       },