Browse Source

feat: 节拍器根据拍号响几声

TIANYONG 2 months ago
parent
commit
3d95282d4b

+ 98 - 10
src/helpers/metronome.ts

@@ -42,6 +42,7 @@ export const metronomeData = reactive({
 	cursorTips: '' as string, // 光标模式提示文字
 	followAudioIndex: 1, // 当前的拍数
 	totalNumerator: 2, // 总拍数
+	firstBeatTypeArr:[] as number[] // 第一小节的节拍
 });
 
 watch(
@@ -331,12 +332,14 @@ 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,
 						stave_x: note?.noteElement?.sourceMeasure?.verticalMeasureList?.[0]?.stave?.x || 0,
-						end_x: note?.stave?.end_x || 0 || 0,
+						end_x: note?.stave?.end_x || 0,
 						stepList: [] as number[],
 						svgs: [] as any[],
 						isRestFlag: note.isRestFlag,
@@ -370,29 +373,57 @@ class Metronome {
 		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 widthStep = 100 / (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[];
+				// 根据有几个拍子,划分成几份
+				const widthStep = 100 / (beatTypeArr.length+1);
+				// 当前拍子的组合数(2+3+2,3+2)中的数字
+				let beatNum = 0;
 				// console.log('stepList', [...measure.stepList], measure.measureNumberXML)
-				for (let j = 0; j < measure.numerator; j++) {
-					const time = noteStep * j + measure.start;
+				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,
+						isPercent: left?.indexOf("%") > -1, // 是否是根据小节宽度等分
 					});
 				}
 			}
@@ -408,6 +439,63 @@ 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) => {

+ 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)

+ 3 - 0
src/state.ts

@@ -1470,6 +1470,9 @@ function xmlToTracks(xmlString: string) {
   const partNames = Array.from(xmlParse.getElementsByTagName('part-name'));
   return partNames.reduce((arr: string[], item) => {
     const textContent = item?.textContent?.trim()
+    if (textContent?.toLocaleLowerCase() === "common") {
+      (window as any).HasCommonTrack = true;
+    }
     if (textContent?.trim()?.toLocaleLowerCase() !== "common" && textContent) {
       arr.push(textContent)
     }

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

@@ -264,7 +264,7 @@ async function mergeBeatAudio(music?:string){
 		const beatsTime: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 计算的时候 加上空白的时间
 			})
 		})

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

@@ -309,7 +309,14 @@ export default defineComponent({
 											(state.platform === IPlatform.PC && state.zoom > 0.8) ? styles.linePC : '',
 										]}
 										style={item.staveBox}
-										onClick={() => handleSelection(item)}
+										onClick={() => {
+											// 当为连续休止小节的结束选段的时候 应该传休止小节 结束的位置
+											let staveItem = item
+											if(state.section.length === 1 && item.totalMultipleRestMeasures > 0){
+												staveItem = selectData.staves[index + item.totalMultipleRestMeasures - 1]
+											}
+											handleSelection(staveItem)	
+										}}
 									>
 										{/* {lineShow && (
 											<div style={{height: selectData.measureHeight + 'px', position: 'relative'}}>

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

@@ -7,6 +7,7 @@ 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,
@@ -14,7 +15,7 @@ const tickData = reactive({
 	reduceLen: 0,
 	tickEnd: false,
 	/** 节拍器时间 */
-	beatLengthInMilliseconds: 0,
+	beatLengthInMilliseconds: [] as number[],
 	index: 0,
 	show: false
 });
@@ -70,7 +71,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/state.originAudioPlayRate);
 		}
 	});
 };
@@ -94,18 +95,21 @@ 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 +118,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;