Browse Source

Merge branch 'ktyq-online-1.8.7' of http://git.dayaedu.com/liushengqiang/music-score into feature-5.20

TIANYONG 1 year ago
parent
commit
18a0a39ffe

+ 8 - 9
dist/colexiu.html

@@ -2,7 +2,7 @@
 <html lang="en">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-97cd8107.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-0735d98c.js"></script>
 
   <meta charset="UTF-8" />
   <link rel="icon" type="image/svg+xml" href="./vite.svg" />
@@ -40,12 +40,11 @@
       },
     })
   </script>
-  <script type="module" crossorigin src="./js/colexiu-755ffde2.js"></script>
-  <link rel="modulepreload" crossorigin href="./js/index-1c3b512b.js">
-  <link rel="modulepreload" crossorigin href="./js/index-7a726de5.js">
-  <link rel="modulepreload" crossorigin href="./js/index-d4a466e8.js">
-  <link rel="modulepreload" crossorigin href="./js/index-bfe42266.js">
-  <link rel="stylesheet" href="./css/index-ff88379e.css">
+  <script type="module" crossorigin src="./js/colexiu-aeb7966b.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/index-b76327de.js">
+  <link rel="modulepreload" crossorigin href="./js/index-dc67232f.js">
+  <link rel="modulepreload" crossorigin href="./js/index-e6b36152.js">
+  <link rel="stylesheet" href="./css/index-3989851b.css">
   <link rel="stylesheet" href="./css/index-d42b0794.css">
   <link rel="stylesheet" href="./css/colexiu-62f31c4f.css">
   <script type="module">import.meta.url;import("_").catch(()=>1);async function* g(){};window.__vite_is_modern_browser=true;</script>
@@ -57,8 +56,8 @@
   <img id="loading" class="show" src="./loading.svg" alt="loading" />
   
   <script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
-  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-8891b9b0.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/colexiu-legacy-94da7ee2.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-83539fb1.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/colexiu-legacy-399814c2.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 11 - 11
dist/index.html

@@ -2,7 +2,7 @@
 <html lang="ZH-cn">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-97cd8107.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-0735d98c.js"></script>
 
   <meta charset="UTF-8">
   <link rel="icon" href="./favicon.ico" />
@@ -75,16 +75,16 @@
       }
     })
   </script>
-  <script type="module" crossorigin src="./js/gym-afce9f9e.js"></script>
-  <link rel="modulepreload" crossorigin href="./js/index-1c3b512b.js">
-  <link rel="modulepreload" crossorigin href="./js/index-7a726de5.js">
-  <link rel="modulepreload" crossorigin href="./js/instruments-4a8f25c3.js">
-  <link rel="modulepreload" crossorigin href="./js/index-c763f5cb.js">
-  <link rel="modulepreload" crossorigin href="./js/index-bfe42266.js">
+  <script type="module" crossorigin src="./js/gym-35d89fd6.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/index-b76327de.js">
+  <link rel="modulepreload" crossorigin href="./js/index-60f46360.js">
+  <link rel="modulepreload" crossorigin href="./js/index-dfb6409c.js">
+  <link rel="modulepreload" crossorigin href="./js/index-dc67232f.js">
+  <link rel="modulepreload" crossorigin href="./js/index-e6b36152.js">
   <link rel="modulepreload" crossorigin href="./js/plyr.min-c8c2777b.js">
-  <link rel="stylesheet" href="./css/index-ff88379e.css">
-  <link rel="stylesheet" href="./css/index-d42b0794.css">
+  <link rel="stylesheet" href="./css/index-3989851b.css">
   <link rel="stylesheet" href="./css/index-85f95688.css">
+  <link rel="stylesheet" href="./css/index-d42b0794.css">
   <link rel="stylesheet" href="./css/plyr-ad8ef5ae.css">
   <link rel="stylesheet" href="./css/index-171cd132.css">
   <script type="module">import.meta.url;import("_").catch(()=>1);async function* g(){};window.__vite_is_modern_browser=true;</script>
@@ -100,8 +100,8 @@
   <img id="loading" class="show" src="./loading.svg" alt="loading" />
   
   <script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
-  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-8891b9b0.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/gym-legacy-fabee3dd.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-83539fb1.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/gym-legacy-e0e5de97.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 9 - 11
dist/instrument.html

@@ -2,7 +2,7 @@
 <html lang="en">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-97cd8107.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-0735d98c.js"></script>
 
   <meta charset="UTF-8" />
   <meta name="viewport"
@@ -40,15 +40,13 @@
       })
     }
   </script>
-  <script type="module" crossorigin src="./js/instrument-ffe2bb74.js"></script>
-  <link rel="modulepreload" crossorigin href="./js/index-1c3b512b.js">
-  <link rel="modulepreload" crossorigin href="./js/index-7a726de5.js">
-  <link rel="modulepreload" crossorigin href="./js/index-d4a466e8.js">
-  <link rel="modulepreload" crossorigin href="./js/instruments-4a8f25c3.js">
-  <link rel="modulepreload" crossorigin href="./js/index-5bbc96b4.js">
-  <link rel="stylesheet" href="./css/index-ff88379e.css">
+  <script type="module" crossorigin src="./js/instrument-7149a1aa.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/index-b76327de.js">
+  <link rel="modulepreload" crossorigin href="./js/index-e6b36152.js">
+  <link rel="modulepreload" crossorigin href="./js/index-60f46360.js">
+  <link rel="stylesheet" href="./css/index-3989851b.css">
   <link rel="stylesheet" href="./css/index-d42b0794.css">
-  <link rel="stylesheet" href="./css/instrument-4e48c51c.css">
+  <link rel="stylesheet" href="./css/instrument-d78850b6.css">
   <script type="module">import.meta.url;import("_").catch(()=>1);async function* g(){};window.__vite_is_modern_browser=true;</script>
   <script type="module">!function(){if(window.__vite_is_modern_browser)return;console.warn("vite: loading legacy chunks, syntax error above and the same error below should be ignored");var e=document.getElementById("vite-legacy-polyfill"),n=document.createElement("script");n.src=e.src,n.onload=function(){System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))},document.body.appendChild(n)}();</script>
 </head>
@@ -66,8 +64,8 @@
 
   
   <script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
-  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-8891b9b0.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/instrument-legacy-43651769.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-83539fb1.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/instrument-legacy-29d21916.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 10 - 12
dist/orchestra.html

@@ -2,7 +2,7 @@
 <html lang="en">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-97cd8107.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-0735d98c.js"></script>
 
   <meta charset="UTF-8" />
   <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
@@ -41,16 +41,14 @@
       transition: opacity .3s;
     }
   </style>
-  <script type="module" crossorigin src="./js/orchestra-77b2cbb9.js"></script>
-  <link rel="modulepreload" crossorigin href="./js/index-1c3b512b.js">
-  <link rel="modulepreload" crossorigin href="./js/index-7a726de5.js">
-  <link rel="modulepreload" crossorigin href="./js/index-d4a466e8.js">
-  <link rel="modulepreload" crossorigin href="./js/index-c763f5cb.js">
-  <link rel="modulepreload" crossorigin href="./js/index-bfe42266.js">
-  <link rel="modulepreload" crossorigin href="./js/index-5bbc96b4.js">
-  <link rel="stylesheet" href="./css/index-ff88379e.css">
-  <link rel="stylesheet" href="./css/index-d42b0794.css">
+  <script type="module" crossorigin src="./js/orchestra-7195cbdb.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/index-b76327de.js">
+  <link rel="modulepreload" crossorigin href="./js/index-dfb6409c.js">
+  <link rel="modulepreload" crossorigin href="./js/index-dc67232f.js">
+  <link rel="modulepreload" crossorigin href="./js/index-e6b36152.js">
+  <link rel="stylesheet" href="./css/index-3989851b.css">
   <link rel="stylesheet" href="./css/index-85f95688.css">
+  <link rel="stylesheet" href="./css/index-d42b0794.css">
   <link rel="stylesheet" href="./css/orchestra-8bc1a9c0.css">
   <script type="module">import.meta.url;import("_").catch(()=>1);async function* g(){};window.__vite_is_modern_browser=true;</script>
   <script type="module">!function(){if(window.__vite_is_modern_browser)return;console.warn("vite: loading legacy chunks, syntax error above and the same error below should be ignored");var e=document.getElementById("vite-legacy-polyfill"),n=document.createElement("script");n.src=e.src,n.onload=function(){System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))},document.body.appendChild(n)}();</script>
@@ -72,8 +70,8 @@
   </script>
   
   <script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
-  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-8891b9b0.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/orchestra-legacy-093b5b74.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-83539fb1.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/orchestra-legacy-4f4e3ad4.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 6 - 6
dist/report-share.html

@@ -2,7 +2,7 @@
 <html lang="en">
 
 <head>
-  <script type="module" crossorigin src="./js/polyfills-97cd8107.js"></script>
+  <script type="module" crossorigin src="./js/polyfills-0735d98c.js"></script>
 
   <meta charset="UTF-8" />
   <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
@@ -25,10 +25,10 @@
       transition: opacity .3s;
     }
   </style>
-  <script type="module" crossorigin src="./js/report-share-7571c083.js"></script>
-  <link rel="modulepreload" crossorigin href="./js/index-1c3b512b.js">
+  <script type="module" crossorigin src="./js/report-share-d42068ed.js"></script>
+  <link rel="modulepreload" crossorigin href="./js/index-b76327de.js">
   <link rel="modulepreload" crossorigin href="./js/plyr.min-c8c2777b.js">
-  <link rel="stylesheet" href="./css/index-ff88379e.css">
+  <link rel="stylesheet" href="./css/index-3989851b.css">
   <link rel="stylesheet" href="./css/plyr-ad8ef5ae.css">
   <link rel="stylesheet" href="./css/report-share-0f4c3151.css">
   <script type="module">import.meta.url;import("_").catch(()=>1);async function* g(){};window.__vite_is_modern_browser=true;</script>
@@ -51,8 +51,8 @@
   </script>
   
   <script nomodule>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script>
-  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-8891b9b0.js"></script>
-  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/report-share-legacy-e68dde79.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
+  <script nomodule crossorigin id="vite-legacy-polyfill" src="./js/polyfills-legacy-83539fb1.js"></script>
+  <script nomodule crossorigin id="vite-legacy-entry" data-src="./js/report-share-legacy-66ddce4d.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
 </body>
 
 </html>

+ 1 - 1
src/helpers/customMusicScore.ts

@@ -849,7 +849,7 @@ export const setGlobalMusicSheet = () => {
 	const customAccentItem = customAccentList.find(({id, part_index}) => {
 	  return id == state.cbsExamSongId && part_index == partIndex
 	})
-	if (customAccentItem) {
+	if (customAccentItem || state.isEvxml) {
 	  setGlobalData('customAccentItem', true)
 	}
 	/** 全声部声部 +  */

+ 188 - 10
src/helpers/formateMusic.ts

@@ -17,6 +17,11 @@ const browserInfo = browser();
 dayjs.extend(duration);
 
 /**
+ * 需要隐藏的中文速度文本
+ */
+const hideSpeedWords: string[] = ["中速"];
+
+/**
  * 获取节拍器的时间
  * @param speed 速度
  * @param firstMeasure 曲谱第一个小节
@@ -378,6 +383,7 @@ export const onlyVisible = (xml: string, partIndex: number): string => {
 	// console.log(visiblePartInfo, partIndex)
 	// 根据后台已选择的分轨筛选出能切换的声轨
 	state.partListNames = partListNames;
+	// console.log('分轨名称',state.partListNames)
 	if (visiblePartInfo) {
 		const id = visiblePartInfo.getAttribute("id");
 		Array.from(parts).forEach((part: any) => {
@@ -615,6 +621,7 @@ export const formatZoom = (num = 1) => {
  */
 export const formatXML = (xml: string, xmlUrl?: string): string => {
 	if (!xml) return "";
+	
 	const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
 	const measures = Array.from(xmlParse.getElementsByTagName("measure"));
 	const repeats: any = Array.from(xmlParse.querySelectorAll('repeat'));
@@ -624,6 +631,7 @@ export const formatXML = (xml: string, xmlUrl?: string): string => {
 	// 解析处理evxml
 	if (state.isEvxml) {
 		analyzeEvxml(xmlParse, xmlUrl);
+		customizationXml(xmlParse);
 	}
 	// const words: any = xmlParse.getElementsByTagName("words");
 	// for (const word of words) {
@@ -655,11 +663,30 @@ export const formatXML = (xml: string, xmlUrl?: string): string => {
 				// if (note.getElementsByTagName("space").length && !note.getElementsByTagName("duration").length) {
 				// 	measure.removeChild(note);
 				// }
-				if (!note.getElementsByTagName("duration").length || (note.getElementsByTagName("duration").length && note.getElementsByTagName("duration")[0]?.textContent == 0)) {
-					measure.removeChild(note);
+				// 非倚音音符
+				if (!note.getElementsByTagName("grace").length) {
+					if (!note.getElementsByTagName("duration").length || (note.getElementsByTagName("duration").length && note.getElementsByTagName("duration")[0]?.textContent == 0)) {
+						measure.removeChild(note);
+					}
 				}
 			});
 		}
+		// 如果有特殊中文速度文本,需要删除
+		const reg = new RegExp("[\\u4E00-\\u9FFF]+", "g");
+		if (measure.getElementsByTagName("words").length && state.isEvxml) {
+			const wordList = Array.from(measure.getElementsByTagName("words")) || [];
+			wordList.forEach((word: any) => {
+				// TODO:删除妙极客曲子无意义的words
+				// wordArr?.push(word?.textContent)
+				if (word?.textContent && reg.test(word?.textContent) && word?.parentNode?.parentNode) {
+					measure.removeChild(word.parentNode.parentNode);
+					// deleteWordArr?.push(word?.textContent)
+				}
+				// if(hideSpeedWords.includes(word?.textContent) && word?.parentNode?.parentNode) {
+				// 	measure.removeChild(word.parentNode.parentNode);
+				// }
+			})
+		}
 		if (measure.getElementsByTagName("note").length === 0) {
 			const forwardTimeElement = measure.getElementsByTagName("forward")[0]?.getElementsByTagName("duration")[0];
 			if (forwardTimeElement) {
@@ -745,6 +772,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 	let staveNoteIndex = 0;
 	let staveIndex = 0;
 
+	let preNoteEndTime = 0; // 上一个音符的结束时间
+
 	const _notes = [] as any[];
 	if (state.gradualTimes) {
 		console.log("后台设置的渐慢小节时间", state.gradual, state.gradualTimes);
@@ -754,6 +783,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 	const currentTimes = [] as any[];
 	let isSetNextNoteReal = false;
 	let differFrom = 0;
+	// let testIdx = 0;
+	let repeatIdx = 0; // 循环的次数
 	while (!iterator.EndReached) {
 		// console.log({ ...iterator });
 		const voiceEntries = iterator.CurrentVoiceEntries?.[0] ? [iterator.CurrentVoiceEntries?.[0]] : [];
@@ -837,10 +868,18 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 		}
 
 		iterator.moveToNextVisibleVoiceEntry(false);
+		// 从头开始循环,repeatIdx标记+1
+		if (iterator.backJumpOccurred) {
+			repeatIdx += 1;
+		}
+		iterator.repeatIdx = repeatIdx;
+		// console.log('小节',testIdx,iterator.repeatIdx,iterator.EndReached,iterator.currentMeasureIndex,iterator.backJumpOccurred,iterator.forwardJumpOccurred)
+		// testIdx += 1;
 	}
 	// 是否是变速的曲子
 	const hasVaryingSpeed = _notes.some((item: any) => item.measuresTempoInBPM !== _notes[0].measuresTempoInBPM)
-	console.log('变速曲子',hasVaryingSpeed)
+	console.log('变速曲子',hasVaryingSpeed, _notes)
+	let noteIds: any = [];
 	// let voicesBBox: any = null;
 	for (let { note, iterator, currentTime, isDouble, isMutileSubject } of _notes) {
 		if (note) {
@@ -920,6 +959,12 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			}
 
 			let relativeTime = usetime;
+
+			// 妙极客的曲子,修复有的音符有times,有的音符没有times导致的,累计时长错误问题
+			if (state.isEvxml && relativeTime < preNoteEndTime - fixtime) {
+				relativeTime = preNoteEndTime - fixtime
+			}
+
 			let beatSpeed = 0;
 			// 速度不能为0 此处的速度应该是按照设置的速度而不是校准后的速度,否则mp3速度不对
 			if (measureSpeed !== baseSpeed && !hasVaryingSpeed) {
@@ -1094,6 +1139,21 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			// console.log(note.tie)
 			// console.log('👀看看endtime', duration, relaEndtime, fixtime, i)
 			// console.log('频率',note?.pitch?.frequency,i)
+			/**
+			 * evxml的曲子,如果曲谱xml中带有times信息,则音符时值优先取times中的值
+			 */
+			let evNoteStartTime = 0, evNoteEndTime = 0;
+			if (state.isEvxml && note?.noteTimeInfo?.length) {
+				const idx = noteIds.filter((item: any) => item === svgElement?.attrs.id)?.length || 0
+				evNoteStartTime = note?.noteTimeInfo[idx]?.begin
+				evNoteEndTime = note?.noteTimeInfo[idx]?.end
+				if (evNoteStartTime) {
+					relativeTime = evNoteStartTime - fixtime
+					// usetime = evNoteStartTime - fixtime
+				}
+				// usetime = evNoteStartTime - fixtime
+			}
+			svgElement?.attrs.id && noteIds.push(svgElement?.attrs.id)
 
 			const nodeDetail = {
 				isStaccato: note.voiceEntry.isStaccato(),
@@ -1122,8 +1182,8 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				note: note.halfTone + 12, // see issue #224
 				fixtime, // 弱起补充的时间
 				relativeTime: retain(relativeTime),
-				time: retain(relativeTime + fixtime), // 开始播放的时间
-				endtime: retain(relaEndtime + fixtime), // 播放完成的时间
+				time: state.isEvxml && evNoteStartTime ? retain(evNoteStartTime) : retain(relativeTime + fixtime), // 开始播放的时间
+				endtime: state.isEvxml && evNoteEndTime ? retain(evNoteEndTime) : retain(relaEndtime + fixtime), // 播放完成的时间
 				relaEndtime: retain(relaEndtime),
 				realValue,
 				halfTone: note.halfTone,
@@ -1141,7 +1201,22 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				totalMultipleRestMeasures, // 当前小节总的合并小节数
 				measureSpeed,  // 小节速度
 				maxNoteNum: note.maxNoteNum, // 当前小节音符最多的分轨的音符数量
+				repeatIdx: iterator.repeatIdx, // 标记是第几遍循环,从0开始
 			};
+			// 如果是妙极客的曲子,并且第二遍循环播放需要等待时间,并且是第二遍循环的第一个小节的第一个音符
+			// if (state.isEvxml && state.secondEvXmlBeginTime && nodeDetail.i > 0 && nodeDetail.MeasureNumberXML === 1 && nodeDetail.noteId === 0) {
+			// 	nodeDetail.time = nodeDetail.time + state.secondEvXmlBeginTime;
+			// 	nodeDetail.endtime = nodeDetail.endtime + state.secondEvXmlBeginTime;
+			// 	usetime = usetime + state.secondEvXmlBeginTime;
+			// 	relativeTime = relativeTime + state.secondEvXmlBeginTime;
+			// }
+			if (state.isEvxml && nodeDetail.repeatIdx && nodeDetail.i > 0 && nodeDetail.MeasureNumberXML === 1 && nodeDetail.noteId === 0) {
+				const currentWaitTime = state.evXmlBeginArr[nodeDetail.repeatIdx] || 0;
+				nodeDetail.time = nodeDetail.time + currentWaitTime;
+				nodeDetail.endtime = nodeDetail.endtime + currentWaitTime;
+				usetime = usetime + currentWaitTime;
+				relativeTime = relativeTime + currentWaitTime;
+			}			
 			nodeDetail.realKey = formatRealKey(note.halfTone - fixedKey * 12, nodeDetail);
 			nodeDetail.duration = nodeDetail.endtime - nodeDetail.time;
 			let tickables = activeVerticalMeasureList[0]?.vfVoices["1"]?.tickables || [];
@@ -1150,6 +1225,7 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 			}
 			// console.log(note.sourceMeasure.MeasureNumberXML, note.sourceMeasure.verticalSourceStaffEntryContainers.length)
 			// console.log('👀看看endtime', nodeDetail.duration, relaEndtime, fixtime, i)
+			// console.log('音符时间',nodeDetail.i,nodeDetail.time,nodeDetail.endtime)
 			tickables = tickables.filter((tickable: any) => tickable.attrs?.type !== "GhostNote")
 			const maxNum = (state.isCombineRender && note.maxNoteNum) ? note.maxNoteNum : tickables.length;
 			nodeDetail.noteLength = maxNum || 1;
@@ -1167,11 +1243,14 @@ export const formateTimes = (osmd: OpenSheetMusicDisplay) => {
 				relaMeasureLength = 0;
 				measures = [];
 			}
+			preNoteEndTime = nodeDetail.endtime;
 		}
 		i++;
 	}
 	// 按照时间轴排序
 	const sortArray = allNotes.sort((a, b) => a.relativeTime - b.relativeTime).map((item, index) => ({ ...item, i: index }));
+	// const sortArray = allNotes.sort((a, b) => a.time - b.time).map((item, index) => ({ ...item, i: index }));
+	// const sortArray = allNotes.map((item, index) => ({ ...item, i: index }));
 	console.timeEnd("音符跑完时间");
 	try {
 		osmd.cursor.reset();
@@ -1258,19 +1337,118 @@ export const verifyCanRepeat = (startNum: number, endNum: number) => {
 	}
 }
 
+// 处理妙极客xml谱面
+const customizationXml = (xmlParse: any) => {
+	const credits: any = Array.from(xmlParse.querySelectorAll('credit'));
+	const creators: any = Array.from(xmlParse.querySelectorAll('creator'));
+	const graces: any = Array.from(xmlParse.querySelectorAll('grace'));
+	const measures: any[] = Array.from(xmlParse.getElementsByTagName("measure"));
+	const notes: any[] = Array.from(xmlParse.getElementsByTagName("note"));
+
+	// 获取音符最多的歌词数,用于自定义循环播放次数
+	let maxLyricNum = 0;
+	if (notes && notes.length) {
+		for (const note of notes) {
+			if (maxLyricNum < note.getElementsByTagName("lyric").length) {
+				maxLyricNum = note.getElementsByTagName("lyric").length
+			}
+		}
+	}
+	state.maxLyricNum = maxLyricNum;
+	// state.osmd.EngravingRules.DYCustomRepeatCount = maxLyricNum;
+	;(window as any).DYCustomRepeatCount = state.maxLyricNum;
+	console.log('歌词次数',maxLyricNum)
+
+	if (credits && credits.length) {
+		for (const credit of credits) {
+			if (credit.getElementsByTagName("credit-type")?.[0]?.textContent === 'lyricist') {
+				const creditWord = credit.getElementsByTagName("credit-words")
+				creditWord?.[0].setAttribute('justify', 'right')
+			}
+		}
+	}
+	if (creators && creators.length) {
+		for (const creator of creators) {
+			if (creator.getAttribute('type') === 'lyricist') {
+				// creator.textContent = '测试一下1';
+			}
+			
+		}
+	}
+	// 妙极客xml的倚音(grace)标签需要加上slash=yes属性
+	if (graces && graces.length) {
+		for (const grace of graces) {
+			grace?.setAttribute('slash','yes');
+			// console.log(grace,'倚音')
+		}
+	}
+	// 妙极客xml部分小节没有音符,只有Segno,该小节不需要渲染,表示的是反复标记
+	for (const measure of measures) {
+		const hasNote = measure.getElementsByTagName("note").length;
+		const hasSegno = measure.getElementsByTagName("segno").length;
+		const sounds = Array.from(measure.getElementsByTagName("sound"));
+		const hasSoundSegno = sounds.some((item: any) => item.getAttribute('segno') === 'segno' );
+		if (!hasNote && hasSegno && hasSoundSegno) {
+			const parent = measure.parentNode;
+			parent.removeChild(measure);
+		}
+	}
+
+	/**
+	 * bug: #10289,曲目:1782672015612725196、1788040971888537602
+	 * 妙极客xml,多遍歌词循环的曲目,如果没有repeat标签,需要加上repeat标签
+	 * */
+	if (maxLyricNum > 1) {
+		const hasRepeat = xmlParse.querySelectorAll('repeat').length > 0
+		if (!hasRepeat) {
+			const lastMeasure = measures.last();
+			if (lastMeasure.getElementsByTagName('barline').length) {
+				const barlineDom = lastMeasure.getElementsByTagName('barline')[0]
+				barlineDom.innerHTML = barlineDom.innerHTML + `<repeat direction="backward" />`;
+			} else {
+				lastMeasure.innerHTML = lastMeasure.innerHTML + `
+				<barline location="right">
+					<bar-style>light-heavy</bar-style>
+					<repeat direction="backward" />
+				</barline>`
+			}
+			// console.log(lastMeasure)
+		}
+	}
+}
+
 // 计算evxml的起始播放时间
 const analyzeEvxml = (xmlParse: any, xmlUrl?: string) => {
 	// xml拍号数
 	const xmlNum = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[0]?.getAttribute('num');
+	const denNum = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[0]?.getAttribute('den');
+	const xmlNum2 = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[1]?.getAttribute('num');
+	const denNum2 = xmlParse.getElementsByTagName("timegap")[0]?.getElementsByTagName("values")[0]?.getElementsByTagName("item")[1]?.getAttribute('den');
+	const timeGaps: any = xmlParse.getElementsByTagName("timegap")?.length ? Array.from(xmlParse.getElementsByTagName("timegap")?.[0]?.getElementsByTagName("values")?.[0]?.getElementsByTagName("item")) : [];
 	// 第一个音符的起始时间
-	const firstNoteBeginTime = xmlParse.getElementsByTagName("times")[0]?.getElementsByTagName("time")[0]?.getAttribute('begin');
-	state.evXmlBeginTime = firstNoteBeginTime ? firstNoteBeginTime / 1000 : xmlNum ? 60 / state.originSpeed * xmlNum : 0;
-	const hasTimeGap = xmlParse.getElementsByTagName("timegap").length > 0;
-	const hasTimes = xmlParse.getElementsByTagName("times").length > 0;
+	const firstMeasure = xmlParse.getElementsByTagName("measure")[0];
+	if (firstMeasure) {
+		const firstNoteBeginTime = firstMeasure.getElementsByTagName("times")[0]?.getElementsByTagName("time")[0]?.getAttribute('begin');
+		state.evXmlBeginTime = firstNoteBeginTime ? firstNoteBeginTime / 1000 : xmlNum ? 60 / state.originSpeed * xmlNum * 4/denNum : 0;
+		state.secondEvXmlBeginTime = firstNoteBeginTime ? 0 : xmlNum2 ? 60 / state.originSpeed * xmlNum2 * 4/denNum2 : 0;
+		const hasTimeGap = xmlParse.getElementsByTagName("timegap").length > 0;
+		const hasTimes = xmlParse.getElementsByTagName("times").length > 0;
+
+		if (timeGaps && timeGaps.length && !firstNoteBeginTime) {
+			for (const timeGap of timeGaps) {
+				const num: any = timeGap?.getAttribute('num'), den: any = timeGap?.getAttribute('den');
+				const startTime = num ? 60 / state.originSpeed * num * 4/den : 0;
+				state.evXmlBeginArr.push(startTime)
+			}
+		}
+
+		console.log('🚀 ~ evxml解析','有timegap:',hasTimeGap,'有times:',hasTimes,'timegap集合',state.evXmlBeginArr,'第一个timegap',state.evXmlBeginTime)
+	}
+
 	// if (!hasTimeGap && !hasTimes) {
 	// 	state.noTimes.push(xmlUrl)
 	// }
-	console.log('🚀 ~ evxml解析','有timegap:',hasTimeGap,'有times:',hasTimes)
+	
 }
 
 /**

BIN
src/page-instrument/component/the-music-list/icon-music-vip.png


+ 47 - 7
src/page-instrument/component/the-music-list/index.module.less

@@ -49,7 +49,8 @@
             overflow-x: hidden;
             overflow-y: auto;
         }
-        .van-tab--active::after{
+
+        .van-tab--active::after {
             content: '';
             position: absolute;
             bottom: 0;
@@ -60,7 +61,8 @@
             background: var(--van-tabs-bottom-bar-color);
             border-radius: var(--van-tabs-bottom-bar-height);
         }
-        .van-tabs__line{
+
+        .van-tabs__line {
             transition-duration: 0s !important;
             display: none;
         }
@@ -74,21 +76,59 @@
 .item {
     display: flex;
     align-items: center;
-    height: 41px;
-    border-radius: 7px;
+    // height: 41px;
+    // border-radius: 7px;
     font-size: 13px;
     font-family: PingFangSC-Regular, PingFang SC;
     font-weight: 400;
     color: #333333;
     line-height: 18px;
-    padding: 0 14px;
-    margin: 10px 0;
+    padding: 12px 12px;
+    // margin: 10px 0;
+
+    .titleImg {
+        width: 51px;
+        height: 51px;
+        margin-right: 10px;
+        border-radius: 9px !important;
+        overflow: hidden;
+        position: relative;
+        flex-shrink: 0;
+    }
+
+    .iconType {
+        position: absolute;
+        width: 28px;
+        height: 14px;
+        right: 0;
+        top: 0;
+        z-index: 9;
+        border-top-right-radius: 8px !important;
+
+        // &.FREE {
+        //   background: url('../co-ai/image/icon-music-default.png') no-repeat center;
+        //   background-size: contain;
+        // }
+
+        &.VIP {
+            background: url('./icon-music-vip.png') no-repeat center;
+            background-size: contain;
+        }
+    }
+
+    .author {
+        padding-top: 8px;
+        font-size: 13px;
+        color: #777777;
+        line-height: 1;
+    }
 }
 
 .itemActive {
     background: #ECF9FF;
 }
-.noData{
+
+.noData {
     display: flex;
     justify-content: center;
     align-items: center;

+ 98 - 94
src/page-instrument/component/the-music-list/list.tsx

@@ -2,104 +2,108 @@ import { defineComponent, onMounted, reactive, watch } from "vue";
 import styles from "./index.module.less";
 import { api_musicSheetPage } from "../../api";
 import state, { togglePlay } from "/src/state";
-import { List } from "vant";
+import { List, Image } from "vant";
 import { postMessage } from "/src/utils/native-message";
 import qs from "query-string";
 
 export default defineComponent({
-	name: "TheMusicList-list",
-	props: {
-		recentFlag: {
-			type: Boolean,
-			default: false,
-		},
-	},
-	setup(props) {
-		const forms = reactive({
-			page: 1,
-			rows: 20,
-			musicSheetCategoriesId: state.bizMusicCategoryId,
-			recentFlag: props.recentFlag ? true : null,
-			excludeMusicId: props.recentFlag ? null : state.examSongId,
-		});
-		const data = reactive({
-			list: [] as any[],
-			finished: false,
-			loading: false,
-			hasNext: true,
-		});
-		const getList = async () => {
-			if (!data.hasNext) return
-			data.loading = true;
-			try {
-				const res = await api_musicSheetPage({
-					...forms,
-				});
-				if (res?.code === 200 && Array.isArray(res.data?.rows)) {
-					data.list = [...data.list, ...res.data.rows];
-				}
-				data.finished = res.data?.rows?.length < forms.rows;
-				data.hasNext = res.data?.total > data.list.length
-			} catch (error) {
-				console.log(error);
-			}
+  name: "TheMusicList-list",
+  props: {
+    recentFlag: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  setup(props) {
+    const forms = reactive({
+      page: 1,
+      rows: 20,
+      musicSheetCategoriesId: state.bizMusicCategoryId,
+      recentFlag: props.recentFlag ? true : null,
+      excludeMusicId: props.recentFlag ? null : state.examSongId,
+    });
+    const data = reactive({
+      list: [] as any[],
+      finished: false,
+      loading: false,
+      hasNext: true,
+    });
+    const getList = async () => {
+      if (!data.hasNext) return;
+      data.loading = true;
+      try {
+        const res = await api_musicSheetPage({
+          ...forms,
+        });
+        if (res?.code === 200 && Array.isArray(res.data?.rows)) {
+          data.list = [...data.list, ...res.data.rows];
+        }
+        data.finished = res.data?.rows?.length < forms.rows;
+        data.hasNext = res.data?.total > data.list.length;
+      } catch (error) {
+        console.log(error);
+      }
 
-			data.loading = false;
-		};
-		watch(
-			() => props.recentFlag,
-			() => {
-				data.hasNext = true
-			}
-		);
-		onMounted(() => {
-			getList();
-		});
+      data.loading = false;
+    };
+    watch(
+      () => props.recentFlag,
+      () => {
+        data.hasNext = true;
+      }
+    );
+    onMounted(() => {
+      getList();
+    });
 
-		const openAccomapina = (item: any) => {
-			if (item.id === state.examSongId) return;
-			// 暂停播放
-			togglePlay("paused");
-			postMessage({
-				api: "cloudLoading",
-				content: {
-					show: true,
-					type: "fullscreen",
-				},
-			});
-			location.href =
-				location.origin +
-				location.pathname +
-				"?" +
-				qs.stringify({
-					id: item.id,
-					_t: Date.now(),
-				});
-		};
-		return () => (
-			<div class={styles.wrap}>
-				<List
-					loading={data.loading}
-					finished={data.finished}
-					immediateCheck={false}
-					onLoad={() => {
-						forms.page++;
-						getList();
-					}}
-				>
-					{data.list.map((item: any) => {
-						return (
-							<div
-								class={[styles.item, state.examSongId == item.id && styles.itemActive]}
-								onClick={() => openAccomapina(item)}
-							>
-								{item.musicSheetName}
-							</div>
-						);
-					})}
-					{!data.loading && data.list.length === 0 && <div class={styles.noData}>暂无数据</div>}
-				</List>
-			</div>
-		);
-	},
+    const openAccomapina = (item: any) => {
+      if (item.id === state.examSongId) return;
+      // 暂停播放
+      togglePlay("paused");
+      postMessage({
+        api: "cloudLoading",
+        content: {
+          show: true,
+          type: "fullscreen",
+        },
+      });
+      location.href =
+        location.origin +
+        location.pathname +
+        "?" +
+        qs.stringify({
+          id: item.id,
+          _t: Date.now(),
+        });
+    };
+    return () => (
+      <div class={styles.wrap}>
+        <List
+          loading={data.loading}
+          finished={data.finished}
+          immediateCheck={false}
+          onLoad={() => {
+            forms.page++;
+            getList();
+          }}
+        >
+          {data.list.map((item: any) => {
+            return (
+              <div class={[styles.item, state.examSongId == item.id && styles.itemActive]} onClick={() => openAccomapina(item)}>
+                <div class={styles.titleImg}>
+                  <i class={[styles.iconType, styles[item.paymentType]]}></i>
+                  <Image src={item.titleImg} class={styles.img} />
+                </div>
+                <div class={styles.content}>
+                  <p class={styles.name}>{item.musicSheetName}</p>
+                  {item.composer && <p class={styles.author}>{item.composer}</p>}
+                </div>
+              </div>
+            );
+          })}
+          {!data.loading && data.list.length === 0 && <div class={styles.noData}>暂无数据</div>}
+        </List>
+      </div>
+    );
+  },
 });

+ 1 - 0
src/page-instrument/custom-plugins/guide-page/student-top.tsx

@@ -182,6 +182,7 @@ export default defineComponent({
 
   const guideInfo = ref({} as any)
   const getAllGuidance = async()=>{
+    console.log('学生引导123')
     try{
 			if (state.guideInfo) {
 				guideInfo.value = state.guideInfo

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

@@ -226,7 +226,9 @@ export default defineComponent({
     /** 连接websocket */
     const handleConnect = async () => {
       const behaviorId = localStorage.getItem("behaviorId") || localStorage.getItem("BEHAVIORID") || undefined;
-      const rate = state.speed / state.originSpeed;
+      let rate = state.speed / state.originSpeed;
+      rate = parseFloat(rate.toFixed(2));
+      console.log('速度比例',rate,'速度',state.speed)
       calculateInfo = formatTimes()
       const content = {
         musicXmlInfos: calculateInfo.datas,
@@ -245,6 +247,7 @@ export default defineComponent({
         // beatLength: Math.round((state.fixtime * 1000) / rate),
         beatLength: actualBeatLength,
         evaluationCriteria: state.evaluationStandard,
+        speedRate: rate, // 播放倍率
       };
       await connectWebsocket(content);
       // state.playSource = "music";
@@ -258,12 +261,15 @@ export default defineComponent({
           resetPlaybackToStart()
           return;
         } else if (evaluatingData.resultData?.recordIdStr || evaluatingData.resultData?.recordId) {
+          let rate = state.speed / state.originSpeed;
+          rate = parseFloat(rate.toFixed(2));
           // 上传云端
           // evaluatModel.evaluatUpdateAudio = true;
           api_openAdjustRecording({
             recordId: evaluatingData.resultData?.recordIdStr || evaluatingData.resultData?.recordId,
             title: state.examSongName || "曲谱演奏",
             coverImg: state.coverImg,
+            speedRate: rate, // 播放倍率
           });
           return;
         }

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

@@ -523,7 +523,7 @@ export default defineComponent({
                       headData.speedShow = !headData.speedShow;
                     }}
                   >
-                    <Badge class={styles.badge} content={state.playState === "play" ? state.playIngSpeed : state.speed}>
+                    <Badge class={styles.badge} content={state.playState === "play" ? Math.floor(state.playIngSpeed) : Math.floor(state.speed)}>
                       <img class={styles.iconBtn} src={headImg("icon_speed.svg")} />
                     </Badge>
                     <span>速度</span>

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

@@ -23,13 +23,19 @@ export default defineComponent({
 
 		/** 重置速度 */
 		const resetSpeed = () => {
-			speed.value = state.originSpeed;
+			speed.value = Math.floor(state.originSpeed);
 		};
 
 		watch(
 			() => speed.value,
 			() => {
 				handleSetSpeed(speed.value);
+				// if ( Math.abs(Number(speed.value) - Number(state.speed)) >= 1 ) {
+				// 	speed.value = Math.floor(speed.value)
+				// 	handleSetSpeed(speed.value);
+				// } else {
+				// 	//speed.value = state.speed;
+				// }
 			}
 		);
 

+ 1 - 1
src/page-instrument/view-detail/index.tsx

@@ -168,6 +168,7 @@ export default defineComponent({
       setCustomGradual();
 			setCustomNoteRealValue();
       state.times = formateTimes(osmd);
+      // 一行谱
       if (state.isSingleLine) {
         // 音符添加位置信息bbox
         addNoteBBox(state.times);
@@ -377,7 +378,6 @@ export default defineComponent({
       detailData.fingerPreView = false;
       detailData.fingerPreViewGuide = false;
     };
-    console.log(1111222,state.zoom)
     return () => (
       <div
         class={[styles.detail, state.setting.eyeProtection && "eyeProtection", (state.platform === IPlatform.PC && state.zoom > 0.8) && styles.PC, state.isPreView && styles.preViewDetail, state.isSingleLine && styles.singleLineDetail]}

+ 43 - 11
src/page-instrument/view-evaluat-report/component/share-top/index.module.less

@@ -27,13 +27,16 @@
         height: 24px;
     }
 }
-.disabled{
+
+.disabled {
     //opacity: 0;
     pointer-events: none;
 }
+
 .left {
     display: flex;
     align-items: center;
+
     .leftContent {
         .lcName {
             font-size: 18px;
@@ -42,12 +45,14 @@
             line-height: 25px;
             margin-bottom: 2px;
             padding: 0 !important;
-            :global{
-                .van-notice-bar{
+
+            :global {
+                .van-notice-bar {
                     padding: 0 !important;
                 }
-              }
+            }
         }
+
         .lcScore {
             font-size: 12px;
             color: #777777;
@@ -91,6 +96,7 @@
         &>div:first-child {
             color: var(--van-primary-color) !important;
         }
+
         &>div:last-child {
             background-color: #ECF9FF;
             color: var(--van-primary-color);
@@ -106,7 +112,8 @@
     position: absolute;
     left: 50%;
     top: 50%;
-    transform: translate(-50%,-50%);
+    transform: translate(-50%, -50%);
+
     .cItem {
         width: 64px;
         height: 50px;
@@ -117,21 +124,26 @@
         padding: 4px 0;
         margin: 0 6px;
         cursor: pointer;
+
         .mScore {
             font-size: 16px;
             line-height: 22px;
             color: #AAAAAA;
         }
+
         .mLabel {
             font-size: 12px;
             line-height: 18px;
             color: #AAAAAA;
         }
     }
+
     .active {
         background: #CBEEFF;
         border-radius: 8px;
-        .mScore, .mLabel {
+
+        .mScore,
+        .mLabel {
             color: #000000;
             font-weight: 600;
         }
@@ -153,6 +165,7 @@
         font-weight: 400;
         padding: 0 10px;
         color: #999;
+        cursor: pointer;
 
         .iconBtn {
             display: block;
@@ -225,7 +238,7 @@
     padding: 0 16px;
     height: 30px;
     border-radius: 18px;
-    background-color: rgba(255,255,255, .9);
+    background-color: rgba(255, 255, 255, .9);
     z-index: 1;
     box-sizing: content-box;
 
@@ -233,14 +246,17 @@
         display: flex;
         align-items: center;
         margin-right: 16px;
+
         &:last-child {
             margin-right: 0;
         }
+
         &>span {
             margin-left: 4px;
         }
     }
 }
+
 .shiyiClose {
     width: 30px;
     height: 30px;
@@ -249,13 +265,15 @@
     top: -26px;
     cursor: pointer;
 }
-.shiyiPopup{
+
+.shiyiPopup {
     background: #fff;
     border-radius: 20px;
     width: 80vw;
     max-width: 460px;
     padding: 20px;
     position: relative;
+
     .shiyiTop {
         position: absolute;
         width: 154px;
@@ -264,63 +282,77 @@
         transform: translateX(-50%);
     }
 }
-.shiyiTitle{
+
+.shiyiTitle {
     font-size: 16px;
     color: #333;
     font-weight: 400;
     text-align: center;
 }
+
 .items {
     display: flex;
     flex-wrap: wrap;
     margin-top: 16px;
-    .item{
+
+    .item {
         width: 50%;
         display: flex;
         align-items: center;
         padding: 12px 0 12px 6px;
-        span{
+
+        span {
             margin-left: 12px;
             font-size: 12px;
             font-weight: 400;
         }
+
         svg {
             visibility: visible;
         }
+
         &:nth-child(2n) {
             transform: translateX(20px);
         }
     }
+
     .itemTone {
         width: 50%;
         display: flex;
         align-items: center;
         padding: 16px 0 16px 26px;
         position: relative;
+
         &:nth-child(2n) {
             transform: translateX(20px);
         }
+
         .firstIcon1 {
             width: 12px;
             height: 20px;
         }
+
         .firstIcon2 {
             width: 19px;
             height: 13px;
         }
+
         .firstIcon3 {
             width: 12px;
             height: 13px;
         }
+
         img {
             position: absolute;
             left: 0;
             top: 50%;
             transform: translateY(-50%);
         }
+
         .fiz {
             left: -5px;
         }
+
         span {
             font-size: 12px;
             font-weight: 400;

+ 31 - 3
src/state.ts

@@ -378,6 +378,10 @@ const state = reactive({
   fixtime: 0,
   /** evxml等待播放的时间 */
   evXmlBeginTime: 0,
+  /** 第二遍循环evxml等待播放的时间 */
+  secondEvXmlBeginTime: 0,
+  /** evxml等待播放的时间集合,多遍反复播放,会有多个timegap(前奏)时间 */
+  evXmlBeginArr: [] as any,
   /** 指法信息 */
   fingeringInfo: {} as IFingering,
   /** 滚动容器的ID */
@@ -475,6 +479,8 @@ const state = reactive({
   paymentType: null,
   /** 播放模式,默认练习模式 */
   defaultModeType: 1,
+  /** 音符最多歌词次数 */
+  maxLyricNum: 0,
 });
 const browserInfo = browser();
 let offset_duration = 0;
@@ -509,7 +515,7 @@ const setStep = () => {
 };
 /** 开始播放 */
 export const onPlay = () => {
-  console.log("开始播放");
+  console.log("开始播放",'音频总时长:',getAudioDuration());
   state.playEnd = false;
   // offset_duration = browserInfo.xiaomi ? 0.2 : 0.08;
   offset_duration = 0.2;
@@ -555,7 +561,7 @@ const handlePlaying = () => {
   state.playProgress = (currentTime / duration) * 100;
   let item = getNote(currentTime);
   // console.log(11111,currentTime,duration,state.playSource, item)
-  // console.log(item.i,item.noteId,item.measureSpeed)
+  // console.log(item?.i,item?.noteId,item?.measureSpeed,'播放')
   // 练习模式下,实时刷新小节速度
   if (item && state.modeType === "practise" && state.playState === "play" && item.measureSpeed && item.measureSpeed !== state.playIngSpeed) {
     const ratio = state.speed / state.originSpeed
@@ -563,6 +569,7 @@ const handlePlaying = () => {
   } else if (state.modeType === "practise" && state.playState === "play" && item && !item.measureSpeed) {
     state.playIngSpeed = state.speed
   }
+  state.playIngSpeed = state.playIngSpeed || state.speed;
   if (item) {
     // 选段状态下
     if (state.sectionStatus && state.section.length === 2) {
@@ -906,8 +913,22 @@ export const getNote = (currentTime: number) => {
   }
   let _item = null as any;
   for (let i = state.activeNoteIndex; i < len; i++) {
-    const item = times[i];
+    let item = times[i];
     const prevItem = times[i - 1];
+    // if (state.isEvxml) {
+    //   let diffArr: any[] = [];
+    //   times.forEach((note: any, noteIdx: number) => {
+    //     if (currentTime >= note.time && currentTime <= times[noteIdx+1].time) {
+    //       let diffTime = times[noteIdx+1].time - currentTime;
+    //       diffArr.push({
+    //         diffTime,
+    //         idx: noteIdx
+    //       })
+    //     }
+    //   })
+    //   diffArr.sort((a, b) => a.diffTime - b.diffTime);
+    //   item = diffArr.length ? times[diffArr[0].idx] : item;
+    // }
     if (currentTime >= item.time) {
       if (!prevItem || item.time != prevItem.time) {
         _item = item;
@@ -1347,6 +1368,11 @@ export const followBeatPaly = () => {
 
 // 音符添加bbox
 export const addNoteBBox = (list: any[]) => {
+  const musicContainer = document.getElementById("musicAndSelection")?.getBoundingClientRect() || {
+		x: 0,
+		y: 0,
+	};
+	const parentLeft = musicContainer.x || 0;
   let voicesBBox: any = null;
   for (let i = 0; i < list.length; i++) {
     const note = list[i];
@@ -1358,7 +1384,9 @@ export const addNoteBBox = (list: any[]) => {
     if (svgElement?.attrs.id) {
       // @ts-ignore
       bbox = document.getElementById(`vf-${svgElement?.attrs?.id}`)?.getBBox();
+      const noteBbox = document.getElementById(`vf-${svgElement?.attrs?.id}`)?.getBoundingClientRect?.() || { x: 0, width: 0 };
       bbox = {
+        left: noteBbox.x - parentLeft - noteBbox.width / 4, // 用于简谱模式,跳动音符时,设置光标的位置(五线谱:osmd自动设置光标位置,简谱:需要手动设置光标位置)
         x: bbox?.x * state.zoom,
         y: bbox?.y * state.zoom,
         width: bbox?.width * state.zoom,

+ 3 - 0
src/view/evaluating/index.tsx

@@ -383,9 +383,12 @@ export const handleStartBegin = async (preTimes?: number) => {
 	// 	accompanimentState: state.setting.enableAccompaniment ? 1 : 0,
 	// 	firstNoteTime: preTimes || 0,
 	// });
+	let rate = state.speed / state.originSpeed;
+	rate = parseFloat(rate.toFixed(2));
 	await api_startRecordingCb({
 		accompanimentState: state.setting.enableAccompaniment ? 1 : 0,
 		firstNoteTime: preTimes || 0,
+		speedRate: rate, // 播放倍率
 	}, () => {
 		if (state.isAppPlay) {
 			setTimeout(() => {

+ 5 - 3
src/view/music-score/index.tsx

@@ -101,7 +101,8 @@ export default defineComponent({
 				autoResize: false,
 				followCursor: false,
 				drawPartNames: props.showPartNames, // 是否渲染声轨名称
-				drawComposer: false, // 渲染作者
+				// drawLyricist: false, // 渲染作曲家
+				// drawComposer: false, // 渲染作词家
 				defaultColorMusic: props.musicColor, // 颜色
 				renderSingleHorizontalStaffline: state.isSingleLine ? true : false,
 				autoGenerateMultipleRestMeasuresFromRestMeasures: state.isSingleLine ? false : true, // 连续休止小节是否合并显示
@@ -109,7 +110,6 @@ export default defineComponent({
 				// pageFormat: 'A4_P',
 				// autoBeam: true,
 				// drawMetronomeMarks: false,
-				// drawLyricist: false,
 				// ...this.opotions,
 				
 			});
@@ -119,6 +119,7 @@ export default defineComponent({
 			osmd.EngravingRules.PageTopMargin = state.platform === IPlatform.PC ? 9 : 10; // 老师端顶部间距
 			osmd.EngravingRules.PageTopMarginNarrow = 3;
 			osmd.EngravingRules.PageLeftMargin = 2;
+			osmd.EngravingRules.BreathMarkDistance = 0.1; // 呼吸标记距离音符的位置,百分比
 			// 老师端上课页面,左右两边有功能按钮,所以左右边距需要加大
 			// if (state.isAttendClass && state.platform === IPlatform.PC) {
 			// 	osmd.EngravingRules.PageLeftMargin = 7;
@@ -134,10 +135,11 @@ export default defineComponent({
 				};
 			}
 			osmd.EngravingRules.DYMusicScoreId = state.examSongId || ''
+			osmd.EngravingRules.DYCustomRepeatCount = state.maxLyricNum || 0;
 			await osmd.load(musicData.score);
 			osmd.zoom = state.zoom;
 			osmd.render();
-			// console.log("🚀 ~ osmd:", osmd)
+			console.log("🚀 ~ osmd:", osmd)
 			emit("rendered", osmd);
 			resetFormate();
 			resetGivenFormate();

+ 6 - 3
src/view/music-score/testCheck.tsx

@@ -37,6 +37,8 @@ export const resetRenderMusicScore = (type?: string) => {
 	location.search = "?" + newSearch;
 };
 
+let wordList: never[] = [], deleteWordList: never[] = [];
+
 export default defineComponent({
 	name: "music-score",
 	emits: ["rendered"],
@@ -69,14 +71,15 @@ export default defineComponent({
                 const item = list[i];
                 try {
                     await getXML(item.evxml_file_url);
-                    await init(i);
+                    // await init(i);
                 } catch (error) {
                     errorNum += 1;
                     errorList.push(item.evxml_file_url);
                     console.log('🚀 ~ evxml解析报错:',`第${i}个xml`,error,'总错误数:',errorNum)
                 }
             }
-            console.log('🚀 ~ evxml循环完成','没有times和timegap的:',state.noTimes,'解析报错的xml:',errorList)
+            // console.log('🚀 ~ evxml循环完成','没有times和timegap的:',state.noTimes,'解析报错的xml:',errorList)
+			console.log('关键词',wordList, deleteWordList)
         }
 		/** 设置 曲谱模式,五线谱还是简谱 */
 		const setRenderType = () => {
@@ -88,7 +91,7 @@ export default defineComponent({
 		const getXML = async (evxml: any) => {
 			const res = await fetch(evxml).then((response) => response?.text());
             if (res) {
-                const xml = formatXML(res, evxml);
+                const xml = formatXML(res, evxml, wordList, deleteWordList);
                 musicData.score = state.isCombineRender ? xml : onlyVisible(xml, state.partIndex);
                 if (state.gradualTimes) {
                     state.gradual = getGradualLengthByXml(xml);

File diff suppressed because it is too large
+ 0 - 0
stats.html


+ 2 - 2
vite.config.ts

@@ -76,9 +76,9 @@ export default defineConfig({
         // target: "https://kt.colexiu.com",
         // target: "https://test.lexiaoya.cn",
         // target: "https://kt.colexiu.com",
-        // target: "https://test.resource.colexiu.com", // 内容平台开发环境,内容平台开发,需在url链接上加上isCbs=true
+        target: "https://test.resource.colexiu.com", // 内容平台开发环境,内容平台开发,需在url链接上加上isCbs=true
         // target: "https://dev.resource.colexiu.com",
-        target: "https://test.kt.colexiu.com",
+        // target: "https://test.kt.colexiu.com",
         // target: "https://mec.colexiu.com",
         changeOrigin: true,
         rewrite: (path) => path.replace(/^\/instrument/, ""),

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