Просмотр исходного кода

Merge branch 'iteration-1.0.5-节拍器' into online

liushengqiang 1 год назад
Родитель
Сommit
272392c220

+ 1 - 0
package.json

@@ -21,6 +21,7 @@
     "consola": "^2.15.3",
     "dayjs": "^1.11.7",
     "eventemitter3": "^5.0.0",
+    "file-saver": "^2.0.5",
     "howler": "^2.2.3",
     "html2canvas": "^1.4.1",
     "lodash": "^4.17.21",

+ 7 - 0
pnpm-lock.yaml

@@ -40,6 +40,9 @@ dependencies:
   eventemitter3:
     specifier: ^5.0.0
     version: 5.0.0
+  file-saver:
+    specifier: ^2.0.5
+    version: 2.0.5
   howler:
     specifier: ^2.2.3
     version: 2.2.3
@@ -2772,6 +2775,10 @@ packages:
       reusify: 1.0.4
     dev: true
 
+  /file-saver@2.0.5:
+    resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
+    dev: false
+
   /fill-range@7.0.1:
     resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
     engines: {node: '>=8'}

+ 1 - 0
src/env.d.ts

@@ -5,3 +5,4 @@ declare module "*.vue" {
 
 	export default vueComponent;
 }
+declare module "file-saver"

+ 30 - 160
src/helpers/metronome.ts

@@ -35,12 +35,13 @@ class Metronome {
 		metronomeData.activeList = [];
 	}
 	initPlayer() {
-		if (!this.source1) {
-			this.source1 = this.loadAudio1();
-		}
-		if (!this.source2) {
-			this.source2 = this.loadAudio2();
+		if (!this.source) {
+			this.source = this.loadAudio1();
 		}
+		this.source.volume(metronomeData.disable ? 0 : 1);
+		// if (!this.source2) {
+		// 	this.source2 = this.loadAudio2();
+		// }
 		metronomeData.initPlayerState = true;
 	}
 
@@ -61,20 +62,16 @@ class Metronome {
 		}
 		if (index > -1 && metronomeData.activeIndex !== index) {
 			metronomeData.activeIndex = index;
-			// console.log("播放", metronomeData.activeIndex);
-			metronomeData.activeMetro = this.getStep(activeMetro);
+			// console.log("播放", index);
+			// metronomeData.activeMetro = this.getStep(activeMetro);
 			// console.log("🚀 ~ metronomeData.activeMetro",metronomeData.activeMetro.measureNumberIndex, metronomeData.activeMetro.index)
 			this.playAudio();
-			metronomeData.isClick = false;
 			return;
 		}
-		metronomeData.isClick = false;
 	};
 	// 播放
 	playAudio = () => {
 		if (!metronomeData.initPlayerState) return;
-		this.source = metronomeData.activeMetro?.index === 0 ? this.source1 : this.source2;
-		this.source.volume(metronomeData.disable ? 0 : 1);
 		this.source.play();
 	};
 
@@ -107,76 +104,57 @@ class Metronome {
 	calculation(times: any[]) {
 		// console.log("🚀 ~ times", times);
 		// 1.统计有多少小节
-		const measures: any = {};
+		const measures: any[] = [];
 		let xmlNumber = -1;
+		let measureListIndex = -1;
 		for (let i = 0; i < times.length; i++) {
 			const note = times[i];
 			const measureNumberXML = note?.timeNote?.measureNumber;
 			// console.log("🚀 ~ note?.noteElement?.sourceMeasure", note?.noteElement?.sourceMeasure)
-			// console.log("🚀 ~ measureNumberXML", measureNumberXML, note)
+			// console.log("🚀 ~ measureNumberXML", measureNumberXML , xmlNumber)
 			// console.log("🚀 ~ measureNumberXML", note)
 			if (measureNumberXML > -1) {
-				if (measureNumberXML != xmlNumber) {
+				if (measureNumberXML != measureListIndex) {
 					const m = {
 						measureNumberXML: measureNumberXML,
 						numerator: note?.measure?.numerator || 0,
 						time: note?.timeNote?.millisecondsPerMeasure,
 						stepList: [] as number[],
-						isRestFlag: note.isRestFlag,
-						notes: [] as any[]
+						notes: [note] as any[],
 					};
-					measures[measureNumberXML] = m;
-					xmlNumber = measureNumberXML;
+					xmlNumber++;
+					measures[xmlNumber] = m;
+					measureListIndex = measureNumberXML;
 				} else {
-					measures[measureNumberXML].notes.push(note);
+					measures[xmlNumber].notes.push(note);
 				}
 			}
 		}
-		console.log(measures, measures.length);
-		return
-
-					// 2.统计小节的拍数
-					// 3.统计小节的时长, 开始时间,结束时间
-					// console.log(measureNumberXML,note.measures, times.filter((n: any) => n?.noteElement?.sourceMeasure?.measureListIndex == measureListIndex))
-					// m.stepList = calculateMetroStep(note.measures, m);
-
-		let metroList: number[] = [];
+		// console.log(measures, measures.length);
+		const metroList: number[] = [];
 		const metroMeasure: any[] = [];
 		// 4.按照拍数将时长平均分配
 		try {
 			for (let i = 0; i < measures.length; i++) {
 				const measure = measures[i];
-				const noteStep = measure.time / measure.numerator;
-				// console.log("🚀 ~ measure.measureNumberXML",measure.measureNumberXML, noteStep)
-				const WIDTH =  100;
-				const widthStep = WIDTH / (measure.numerator + 1);
-				metroMeasure[i] = [] as number[];
-				// console.log('stepList', [...measure.stepList], measure.measureNumberXML)
+				const calp = 1 / measure.numerator;
+				// console.log("🚀 ~ measure.numerator:", measure.numerator)
+				const totalMeasureTime = measure.notes.reduce((total: number, note: any) => {
+					return total + note?.abcNote?.duration;
+				}, 0);
+				const step = Math.floor(totalMeasureTime / calp);
+				// console.log('🚀 ~ calp:', calp, 'total', totalMeasureTime, 'step', step)
+				const startTime = measure.notes[0].timeNote.milliseconds;
+				const stepTime = measure.notes[0].timeNote.millisecondsPerMeasure / measure.numerator;
 				for (let j = 0; j < measure.numerator; j++) {
-					const time = noteStep * j + measure.start;
+					const time = stepTime * j + startTime;
 					metroList.push(time);
-					let left = "";
-					if (measure.stepList[j]) {
-						left = measure.stepList[j] + "px";
-					} else {
-						const preLeft = measure.stepList[j - 1];
-						left = !preLeft ? `${widthStep}%` : preLeft.toString().indexOf("%") > -1 ? `${preLeft} + ${widthStep}%` : `${preLeft}px + ${widthStep}%`;
-						measure.stepList[j] = left;
-					}
-					metroMeasure[i].push({
-						index: j,
-						time,
-						// left: (measure.stepList[j] ? measure.stepList[j] + 'px' : (j + 1) * widthStep + '%'),
-						left: left?.indexOf("%") > -1 ? `calc(${left})` : left,
-						measureNumberXML: measure.measureNumberXML,
-						isRestFlag: measure.isRestFlag,
-					});
 				}
 			}
 		} catch (error) {
 			console.log(error);
 		}
-		// console.log(metroList, metroMeasure);
+		// console.log(metroList, metroList.length);
 		// 5.得到所有的节拍时间
 		metronomeData.metroList = metroList;
 		metronomeData.metroMeasure = metroMeasure;
@@ -184,112 +162,4 @@ class Metronome {
 	}
 }
 
-// 计算拍子的时值
-function calculateMetroStep(arr: any[], m: any): number[] {
-	const measureLength = arr.reduce((total: number, item: any) => {
-		total += item._noteLength;
-		return total;
-	}, 0);
-	const clap = measureLength / m.numerator;
-	if (arr.length === 1) {
-		const wholeNote = arr[0].svgElement;
-		if (wholeNote && !wholeNote.isRest()) {
-			const measure_bbox = wholeNote?.attrs?.el?.parentElement?.parentElement?.getBoundingClientRect?.() || { x: 0, right: 0 };
-			let bbox = wholeNote?.attrs?.el?.getBoundingClientRect?.() || { x: 0 };
-			let stepWidth = Math.abs(measure_bbox.right - bbox.x) / m.numerator;
-			let stepList: number[] = [];
-			for (let i = 0; i < m.numerator; i++) {
-				stepList.push(bbox.x - measure_bbox.x + i * stepWidth);
-			}
-			// console.log("🚀 ~ stepList:", stepList, m.measureNumberXML)
-			return stepList;
-		}
-		try {
-			// 开头是休止符
-			if (m.measureNumberXML === 1 && wholeNote && wholeNote.isRest()) {
-				const measure_bbox = wholeNote?.attrs?.el?.parentElement?.parentElement?.getBoundingClientRect?.() || { x: 0, right: 0 };
-				let bbox = wholeNote?.attrs?.el?.getBoundingClientRect?.() || { x: 0 };
-				let stepWidth = Math.abs(measure_bbox.right - bbox.x) / m.numerator;
-				let stepList: number[] = [];
-				for (let i = -1; i < m.numerator - 1; i++) {
-					stepList.push(bbox.x - measure_bbox.x + i * stepWidth);
-				}
-				// console.log(wholeNote?.attrs?.el, m.measureNumberXML)
-				// console.log("🚀 ~ stepList:", stepList, m.measureNumberXML)
-				return stepList;
-			}
-		} catch (error) {
-			console.log("🚀 ~ error:", error);
-		}
-
-		return [];
-	}
-	// console.log("🚀 ~ arr", [...arr],`小节总时值: ${measureLength}`, clap, m.measureNumberXML);
-	let totalLength = 0;
-	let notes: any[] = [];
-	let stepList: number[] = [];
-	for (let i = 0; i < arr.length; i++) {
-		const item = arr[i];
-		item.index = i;
-		const noteLength = item._noteLength;
-		totalLength += noteLength;
-		// 大于一拍
-		const exceedStep = Math.floor(totalLength / clap);
-		// console.log(`note`, item?.svgElement?.attrs?.el,notes.length,{noteLength, exceedStep,clap}, m.measureNumberXML)
-		if (exceedStep >= 1) {
-			totalLength -= clap;
-			// 一拍
-			const measure_bbox = item?.svgElement?.attrs?.el?.parentElement?.parentElement?.getBoundingClientRect?.() || { x: 0 };
-			if (notes.length > 0) {
-				let bbox = notes[0]?.svgElement?.attrs?.el?.getBoundingClientRect?.() || { x: 0 };
-				let x: any = bbox.x - measure_bbox.x;
-				if (notes[0]._noteLength / clap >= 1) {
-					const nextNote = arr[notes[0].index + 1]?.svgElement?.attrs?.el?.getBoundingClientRect?.() || { x: measure_bbox.right } || { x: 0 };
-					const stepWidth = Math.abs(bbox.x - nextNote.x) / 2;
-					x = bbox.x - measure_bbox.x + stepWidth;
-					// console.log(`音符超一拍`, notes[0]?.svgElement?.attrs?.el, arr[notes[0].index + 1]?.svgElement?.attrs?.el, bbox.x - nextNote.x, stepWidth, m.measureNumberXML);
-				}
-				// console.log(`一拍`, notes[0]?.svgElement?.attrs?.el, m.measureNumberXML, notes[0]._noteLength , clap, 'aa')
-				stepList.push(x);
-			} else {
-				let bbox = item?.svgElement?.attrs?.el?.getBoundingClientRect?.() || { x: 0 };
-				let x: any = bbox.x - measure_bbox.x;
-				// console.log(`一拍`, item?.svgElement?.attrs?.el, m.measureNumberXML)
-				stepList.push(x);
-			}
-			notes = [];
-			let bbox = item?.svgElement?.attrs?.el?.getBoundingClientRect?.() || { x: 0 };
-			let x: any = bbox.x - measure_bbox.x;
-			let stepWidth = 0;
-			if (exceedStep > 1) {
-				// 二拍以上
-				const nextNote = arr[i + 1]?.svgElement?.attrs?.el?.getBoundingClientRect?.() || { x: measure_bbox.right } || { x: 0 };
-				stepWidth = Math.abs(bbox.x - nextNote.x) / exceedStep;
-				// console.log("二拍以上 ~ nextNote:",bbox.x , nextNote.x,stepWidth, item?.svgElement?.attrs?.el,arr[i + 1]?.svgElement?.attrs?.el, exceedStep);
-			}
-
-			for (let j = 1; j < exceedStep; j++) {
-				totalLength -= clap;
-				// console.log(`超一拍`,item?.svgElement?.attrs?.el, m.measureNumberXML)
-				stepList.push(x + stepWidth * j);
-			}
-		}
-
-		//有时值就将音符加入
-		if (totalLength > Number.EPSILON && totalLength > 0) {
-			notes.push(item);
-		}
-	}
-	stepList = stepList.reduce((list: any[], n: number) => {
-		if (list.includes(n)) {
-			list.push(undefined as any);
-		} else {
-			list.push(n);
-		}
-		return list;
-	}, []);
-	// console.log("stepList", [...stepList], m.measureNumberXML);
-	return stepList;
-}
-
 export default Metronome;

+ 8 - 0
src/pc/api.ts

@@ -32,3 +32,11 @@ export const api_musicSheetCreationUpdate = (data: any) => {
 export const api_subjectList = () => {
 	return request.post(`/subject/list`);
 };
+
+/** 导入xml */
+export const api_xmlToAbc = (data: any) => {
+	return request.post(`/musicSheetCreation/xmlToAbc`, {
+		requestType: 'form',
+		data: data
+	});
+}

+ 6 - 1
src/pc/home/component/file-btn/index.module.less

@@ -39,10 +39,15 @@
         .n-dropdown-menu {
             min-width: 120px;
         }
+        .n-dropdown-option .n-dropdown-option{
+            min-width: 125px;
+        }
+        
 
         .n-dropdown-option-body {
+            margin: 4px 0;
             padding: 0 12px;
-            --n-option-height: 45px;
+            --n-option-height: 38px;
 
             .n-dropdown-option-body__prefix {
                 display: none;

+ 114 - 110
src/pc/home/component/file-btn/index.tsx

@@ -5,116 +5,120 @@ import { getImage } from "../../images";
 import { DropdownMixedOption } from "naive-ui/es/dropdown/src/interface";
 
 /** 新建 | 保存 | 导入 | 上传 | 导出 | 打印 | 退出*/
-export type IFileBtnType = "newMusic" | "save" | "xml" | "upload" | "png" | "wav" | "midi" | "print" | "exit";
+export type IFileBtnType =
+	| "newMusic"
+	| "save"
+	| "xml"
+	| "upload"
+	| "png"
+	| "wav"
+	| "midi"
+	| "print"
+	| "down-xml"
+	| "exit";
 
 export default defineComponent({
-  name: "FileBtn",
-  emits: ["select"],
-  setup(props, { emit }) {
-    const options: DropdownMixedOption[] = [
-      {
-        label: () => (
-          <div class={styles.dropItem}>
-            <img class={styles.dropIcon} src={getImage("icon_26_4.png")} />
-            <span>新建曲谱</span>
-          </div>
-        ),
-        key: "newMusic",
-      },
-      {
-        label: () => (
-          <div class={styles.dropItem}>
-            <img class={styles.dropIcon} src={getImage("icon_26_0.png")} />
-            <span>保存</span>
-          </div>
-        ),
-        key: "save",
-      },
-      // {
-      // 	label: () => (
-      // 		<div class={styles.dropItem}>
-      // 			<img class={styles.dropIcon} src={getImage("icon_26_0.png")} />
-      // 			<span>导入</span>
-      // 		</div>
-      // 	),
-      // 	key: "import",
-      // 	disabled: true,
-      // 	children: [
-      // 		{
-      // 			label: "xml",
-      //             key: 'xml',
-      // 			disabled: true
-      // 		},
-      // 	],
-      // },
-      {
-        label: () => (
-          <div class={styles.dropItem}>
-            <img class={styles.dropIcon} src={getImage("icon_26_1.png")} />
-            <span>上传到我的资源</span>
-          </div>
-        ),
-        key: "upload",
-        disabled: true,
-      },
-      {
-        label: () => (
-          <div class={styles.dropItem}>
-            <img class={styles.dropIcon} src={getImage("icon_26_2.png")} />
-            <span>导出</span>
-          </div>
-        ),
-        key: "export",
-        children: [
-          {
-            label: "PNG",
-            key: "png",
-          },
-          {
-            label: "WAV",
-            key: "wav",
-          },
-          {
-            label: "MIDI",
-            key: "midi",
-          },
-        ],
-      },
-      {
-        label: () => (
-          <div class={styles.dropItem}>
-            <img class={styles.dropIcon} src={getImage("icon_26_3.png")} />
-            <span>打印</span>
-          </div>
-        ),
-        key: "print",
-        disabled: true,
-      },
-      // {
-      // 	label: () => (
-      // 		<div class={styles.dropItem}>
-      // 			<img class={styles.dropIcon} src={getImage("icon_26_5.png")} />
-      // 			<span>退出</span>
-      // 		</div>
-      // 	),
-      // 	key: "exit",
-      // 	disabled: false,
-      // },
-    ];
-    return () => (
-      <NDropdown
-        class={styles.dropWrap}
-        options={options}
-        trigger="click"
-        onSelect={(val) => {
-          console.log("🚀 ~ val:", val);
-          emit("select", val);
-        }}
-      >
-        <div class={styles.btnImg}>
-          <img class={styles.topBtnIcon} src={getImage("icon_0.png")} />
-        </div>
-      </NDropdown>
-    );
-  },
+	name: "FileBtn",
+	emits: ["select"],
+	setup(props, { emit }) {
+		const options: DropdownMixedOption[] = [
+			{
+				label: () => (
+					<div class={styles.dropItem}>
+						<img class={styles.dropIcon} src={getImage("icon_26_4.png")} />
+						<span>新建曲谱</span>
+					</div>
+				),
+				key: "newMusic",
+			},
+			{
+				label: () => (
+					<div class={styles.dropItem}>
+						<img class={styles.dropIcon} src={getImage("icon_26_0.png")} />
+						<span>保存</span>
+					</div>
+				),
+				key: "save",
+			},
+			{
+				label: () => (
+					<div class={styles.dropItem}>
+						<img class={styles.dropIcon} src={getImage("icon_26_0.png")} />
+						<span>导入</span>
+					</div>
+				),
+				key: "import",
+				// disabled: true,
+				children: [
+					{
+						label: "XML",
+						key: "xml",
+						// disabled: true,
+					},
+				],
+			},
+			{
+				label: () => (
+					<div class={styles.dropItem}>
+						<img class={styles.dropIcon} src={getImage("icon_26_1.png")} />
+						<span>上传到我的资源</span>
+					</div>
+				),
+				key: "upload",
+				disabled: true,
+			},
+			{
+				label: () => (
+					<div class={styles.dropItem}>
+						<img class={styles.dropIcon} src={getImage("icon_26_2.png")} />
+						<span>导出</span>
+					</div>
+				),
+				key: "export",
+				children: [
+					{
+						label: "XML",
+						key: "down-xml",
+					},
+					{
+						label: "PNG",
+						key: "png",
+					},
+					{
+						label: "WAV",
+						key: "wav",
+					},
+					{
+						label: "MIDI",
+						key: "midi",
+					},
+				],
+			},
+			{
+				label: () => (
+					<div class={styles.dropItem}>
+						<img class={styles.dropIcon} src={getImage("icon_26_3.png")} />
+						<span>打印</span>
+					</div>
+				),
+				key: "print",
+				disabled: true,
+			},
+		];
+		return () => (
+			<NDropdown
+				class={styles.dropWrap}
+				options={options}
+				trigger="click"
+				onSelect={(val) => {
+					console.log("🚀 ~ val:", val);
+					emit("select", val);
+				}}
+			>
+				<div class={styles.btnImg}>
+					<img class={styles.topBtnIcon} src={getImage("icon_0.png")} />
+				</div>
+			</NDropdown>
+		);
+	},
 });

+ 13 - 0
src/pc/home/index.module.less

@@ -168,6 +168,18 @@
 }
 
 :global {
+    .n-modal-scroll-content .n-modal-mask{
+        background-color: rgba(0, 0, 0, .6);
+    }
+    .deleteDialog.saveDialog{
+        width: 338px;
+        .n-dialog__title{
+            padding-right: 0 !important;
+        }
+        .n-dialog__content{
+            color: #777;
+        }
+    }
     .abcjs-note-hover {
         &:hover {
             fill: #ffe65948;
@@ -324,6 +336,7 @@
     display: flex;
     justify-content: center;
     align-items: center;
+    z-index: 100;
 }
 .exportPng{
     position: fixed;

+ 205 - 140
src/pc/home/index.tsx

@@ -40,6 +40,7 @@ import {
 	NSelect,
 	NSpace,
 	NSpin,
+	useDialog,
 	useMessage,
 } from "naive-ui";
 import { LongArrowAltDown, LongArrowAltUp, GripLinesVertical } from "@vicons/fa";
@@ -50,85 +51,13 @@ import TheSetting from "./component/the-setting";
 import { useRoute } from "vue-router";
 import { api_musicSheetCreationDetail, api_musicSheetCreationUpdate } from "../api";
 import instrumentsNames from "/src/constant/instrmentsNames.json";
-import { ALL_NOTES, NOTE_DOT } from "./noteData";
+import { ALL_NOTES, ALL_Pitches } from "./noteData";
 import { Close } from "@vicons/ionicons5";
 import { UseDraggable } from "@vueuse/components";
 import { getQuery } from "/src/utils/queryString";
 import Metronome, { metronomeData } from "/src/helpers/metronome";
 import cleanDeep from "clean-deep";
-
-const allPitches = [
-	"C,,,,",
-	"D,,,,",
-	"E,,,,",
-	"F,,,,",
-	"G,,,,",
-	"A,,,,",
-	"B,,,,",
-	"C,,,",
-	"D,,,",
-	"E,,,",
-	"F,,,",
-	"G,,,",
-	"A,,,",
-	"B,,,",
-	"C,,",
-	"D,,",
-	"E,,",
-	"F,,",
-	"G,,",
-	"A,,",
-	"B,,",
-	"C,",
-	"D,",
-	"E,",
-	"F,",
-	"G,",
-	"A,",
-	"B,",
-	"C",
-	"D",
-	"E",
-	"F",
-	"G",
-	"A",
-	"B",
-	"c",
-	"d",
-	"e",
-	"f",
-	"g",
-	"a",
-	"b",
-	"c'",
-	"d'",
-	"e'",
-	"f'",
-	"g'",
-	"a'",
-	"b'",
-	"c''",
-	"d''",
-	"e''",
-	"f''",
-	"g''",
-	"a''",
-	"b''",
-	"c'''",
-	"d'''",
-	"e'''",
-	"f'''",
-	"g'''",
-	"a'''",
-	"b'''",
-	"c''''",
-	"d''''",
-	"e''''",
-	"f''''",
-	"g''''",
-	"a''''",
-	"b''''",
-];
+import { saveAs } from "file-saver";
 
 export const initMusic = (total: number): IMeasure[] => {
 	return new Array(total).fill(0).map((item, index) => {
@@ -164,9 +93,9 @@ export const initMusic = (total: number): IMeasure[] => {
 };
 
 function moveNote(note: string, step: number) {
-	var x = allPitches.indexOf(note);
+	var x = ALL_Pitches.indexOf(note);
 	if (x >= 0) {
-		const _note = allPitches[x - step];
+		const _note = ALL_Pitches[x - step];
 		return _note ? _note : note;
 	}
 	return note;
@@ -175,6 +104,7 @@ function moveNote(note: string, step: number) {
 export default defineComponent({
 	name: "Home",
 	setup() {
+		const dialog = useDialog();
 		const route = useRoute();
 		const message = useMessage();
 		const popup = reactive({
@@ -197,7 +127,7 @@ export default defineComponent({
 		const data = reactive({
 			loading: true,
 			drawCount: 0,
-			isSave: false,
+			isSave: true,
 			musicId: "",
 			musicName: "", // 曲谱名称
 			creator: "", // 创建者
@@ -236,6 +166,8 @@ export default defineComponent({
 			loadingAudioSrouce: false, // 加载音频资源
 			/** 移调类型 */
 			moveKeyType: "inset" as "inset" | "up" | "down", // 移调类型
+			activePlayNote: null as any, // 当前演奏音符
+			times: [] as any[], // 节拍器数据
 		});
 		const noteTypes = ABC_DATA.types.map((item) => item.value).filter(Boolean);
 		const accidentals = ABC_DATA.accidentals.map((item) => item.value).filter(Boolean);
@@ -369,25 +301,34 @@ export default defineComponent({
 				console.log("开始");
 				data.playState = true;
 				var svg = document.querySelector("#paper svg");
-				var cursor = document.createElementNS("http://www.w3.org/2000/svg", "line");
-				cursor.setAttribute("class", "ABCJS-cursor");
+				let cursor = document.querySelector("#paper svg .ABCJS-cursor");
+				if (!cursor) {
+					cursor = document.createElementNS("http://www.w3.org/2000/svg", "line");
+					cursor.setAttribute("class", "ABCJS-cursor");
+					svg?.appendChild(cursor);
+				}
 				cursor.setAttributeNS(null, "x1", "0");
 				cursor.setAttributeNS(null, "y1", "0");
 				cursor.setAttributeNS(null, "x2", "0");
 				cursor.setAttributeNS(null, "y2", "0");
-				svg?.appendChild(cursor);
 			},
 			onBeat: function (beatNumber: any, totalBeats: any, totalTime: any) {
-				// console.log("🚀 ~ beatNumber:", beatNumber, totalBeats, totalTime, abcData.visualObj.getBeatLength());
+				if (!data.playState) return;
+				// console.log("🚀 ~ beatNumber:", (beatNumber / totalBeats) * totalTime);
+				const time = (beatNumber / totalBeats) * totalTime;
+				metronomeData.metro.sound(time);
 			},
-			onEvent: (ev: any) => {
-				// console.log("🚀 ~ ev:", ev);
+			onEvent: (event: any) => {
+				let ev = event as any;
 				if (!data.playState) return;
 				if (ev.measureStart && ev.left === null) return; // this was the second part of a tie across a measure line. Just ignore it.
+
 				if (popup.selectMearesShow) {
 					const startTime = data.selectMeasures.startNote?.currentTrackMilliseconds || 0;
+					// const timeNote = data.times.find(
+					// 	(n) => n.startChar === data.selectMeasures.startNote.startChar
+					// );
 					const endNote: any = data.selectMeasures.endNote ? data.selectMeasures.endNote : null;
-					// console.log("🚀 ~ endNote:", ev.milliseconds , endNote.currentTrackMilliseconds)
 					if (
 						ev.milliseconds < startTime ||
 						(endNote && ev.milliseconds > endNote.currentTrackMilliseconds)
@@ -395,12 +336,12 @@ export default defineComponent({
 						const totalTime = (abcData.synthControl as any).visualObj.getTotalTime();
 						if (totalTime) {
 							const progress = startTime / 1000 / totalTime;
-							nextTick(() => {
-								(abcData.synthControl as any).seek(progress);
-							});
+							(abcData.synthControl as any).seek(progress);
+							return;
 						}
 					}
 				}
+				data.activePlayNote = { ...ev };
 				var cursor = document.querySelector("#paper svg .ABCJS-cursor");
 				if (cursor) {
 					cursor.setAttribute("x1", ev.left + ev.width / 2);
@@ -462,10 +403,23 @@ export default defineComponent({
 				data.playState = true;
 			} else if (type === "pause") {
 				abcData.synthControl.play();
+				// abcData.synthControl.pause();
 				data.playState = false;
 				hideCursor();
+				const totalTime = (abcData.synthControl as any).visualObj.getTotalTime();
+				if (totalTime && data.activePlayNote?.milliseconds !== undefined) {
+					const progress = data.activePlayNote.milliseconds / 1000 / totalTime;
+					nextTick(() => {
+						(abcData.synthControl as any).seek(progress);
+					});
+				}
 			} else {
 				abcData.synthControl.restart();
+				nextTick(() => {
+					if (!data.playState) {
+						abcData.synthControl.play();
+					}
+				});
 			}
 			// console.log("🚀 ~ abcData.synthControl:", abcData.synthControl.timer.noteTimings);
 		};
@@ -519,6 +473,14 @@ export default defineComponent({
 			data.selectMeasures.max = measureNumber;
 		};
 
+		let saveTimer: any = null;
+		const autoSave = () => {
+			saveTimer && clearTimeout(saveTimer);
+			saveTimer = setTimeout(() => {
+				handleSaveMusic(false);
+			}, 5000);
+		};
+
 		/**
 		 * @param isProduct 是否是生成曲谱
 		 */
@@ -528,16 +490,37 @@ export default defineComponent({
 				if (data.playState) {
 					data.playState = false;
 				}
+				data.isSave = false;
+			}
+			if (popup.selectMearesShow) {
+				data.selectMeasures.startNote = null;
+				data.selectMeasures.endNote = null;
+				data.selectMeasures.start = 0;
+				data.selectMeasures.end = 0;
+				popup.selectMearesShow = false;
+				data.selectMeasures.state = false;
+				nextTick(() => {
+					data.selectMeasures.state = true;
+				});
 			}
-
 			return new Promise((resolve) => {
 				nextTick(() => {
 					data.music = renderMeasures(abcData.abc);
 					renderSvg();
 					resetMidi(data.drawCount > 0 ? true : false);
 					renderBoxRect();
-					// productMetronomeData();
+					try {
+						productMetronomeData();
+					} catch (error) {
+						console.log("🚀 ~ error:", error);
+					}
+
 					resolve(1);
+					if (data.drawCount > 0) {
+						const host = location.host;
+						if (host.includes("localhost") || host.includes("192")) return;
+						autoSave();
+					}
 
 					textAreaRef.value && (textAreaRef.value.value = data.music);
 					data.drawCount++;
@@ -548,7 +531,9 @@ export default defineComponent({
 		/** 生成曲谱节拍器数据 */
 		const productMetronomeData = () => {
 			const times = new ABCJS.TimingCallbacks(abcData.visualObj);
+			data.times = times.noteTimings;
 			const list: any[] = [];
+			let meter = abcData.abc.meter || "";
 			// length - 1是为了去除最后一个空的结束事件
 			for (let i = 0; i < times.noteTimings.length - 1; i++) {
 				const timeNote = times.noteTimings[i];
@@ -557,7 +542,10 @@ export default defineComponent({
 				indexStr = indexStr.split(".").map((n: string) => Number(n));
 				if (indexStr.length === 2) {
 					const measure = abcData.abc.measures[indexStr[0]];
-					const meter = measure.meter ?? abcData.abc.meter;
+					// 如果小节里面有拍号,后面一直延用这个拍号,以此类推, ps: 下个版本
+					// if (measure.meter){
+					// 	meter = measure.meter;
+					// }
 					const reg = new RegExp(/M:(\d+)\/\d+/);
 					const numerator = Number(meter.match(reg)?.[1]);
 					// console.log("🚀 ~ reg:", meter.match(reg)?.[1], abcData.abc.meter)
@@ -573,9 +561,12 @@ export default defineComponent({
 					});
 				}
 			}
-			console.log("abcData.abc.measures", list);
-			metronomeData.metro = new Metronome();
+			// console.log("abcData.abc.measures", list);
+			if (!metronomeData.metro) {
+				metronomeData.metro = new Metronome();
+			}
 			try {
+				metronomeData.activeIndex = -1;
 				metronomeData.metro.init(list);
 			} catch (error) {
 				console.log("🚀 ~ 生成节拍器数据错误:", error);
@@ -609,6 +600,32 @@ export default defineComponent({
 			return notes;
 		};
 
+		const handleClose = () => {
+			// 判断是否在应用中
+			console.log("点击退出", window.matchMedia("(display-mode: standalone)").matches);
+			if (window.matchMedia("(display-mode: standalone)").matches) {
+				window.onbeforeunload = null;
+				console.log("准备发消息");
+				window.parent.postMessage(
+					{
+						api: "notation_exit",
+					},
+					"*"
+				);
+			} else {
+				window.close();
+				// 全屏模式无法判断是否在应用打开
+
+				// 那就都发个消息吧
+				window.parent.postMessage(
+					{
+						api: "notation_exit",
+					},
+					"*"
+				);
+			}
+		};
+
 		/**
 		 *
 		 * @param key
@@ -626,26 +643,42 @@ export default defineComponent({
 				abcData.abc.measures[data.active?.measureIndex]?.notes[data.active?.noteIndex] || null;
 
 			if (type === "exit") {
-				// 退出时先保存 不提示
-				await handleSaveMusic(false);
-				// 判断是否在应用中
-				if (window.matchMedia("(display-mode: standalone)").matches) {
-					window.onbeforeunload = null;
-					window.parent.postMessage(
-						{
-							api: "notation_exit",
+				if (!data.isSave) {
+					clearTimeout(saveTimer);
+					dialog.warning({
+						maskClosable: true,
+						autoFocus: false,
+						class: "deleteDialog saveDialog",
+						title: "温馨提示",
+						content: "是否保存当前曲谱?",
+						positiveText: "保存",
+						positiveButtonProps: {
+							type: "primary",
 						},
-						"*"
-					);
-				} else {
-					window.close();
-					window.parent.postMessage(
-						{
-							api: "notation_exit",
+						negativeText: "不保存",
+						negativeButtonProps: {
+							type: "default",
+							ghost: false,
+						},
+						onPositiveClick: async () => {
+							const msg = message.loading("保存中...");
+							await handleSaveMusic(false);
+							setTimeout(() => {
+								msg.type = "success";
+								msg.content = "保存成功";
+								setTimeout(() => {
+									msg.destroy();
+									handleClose();
+								}, 500);
+							}, 300);
+						},
+						onNegativeClick: () => {
+							handleClose();
 						},
-						"*"
-					);
+					});
+					return;
 				}
+				handleClose();
 			}
 			// console.log(params, activeNote);
 			if (type === "type") {
@@ -817,7 +850,7 @@ export default defineComponent({
 
 			// 拍号
 			if (type === "meter") {
-				if (data.active) {
+				if (data.active && data.active.measureIndex !== 0) {
 					if (!activeNote) return;
 					const measure = abcData.abc.measures[data.active.measureIndex];
 					measure.meter = `[${value}]`;
@@ -1008,7 +1041,7 @@ export default defineComponent({
 
 			// 移动音符
 			if (type === "move") {
-				const step = value._step ? value._step : value.action === "up" ? -1 : 1;
+				const step = value.action === "drag" ? value.step : value.action === "up" ? -1 : 1;
 				if (!activeNote) return;
 				activeNote.content = moveNote(activeNote.content, step);
 				// arr now contains elements that are either a chord, a decoration, a note name, or anything else. It can be put back to its original string with .join("").
@@ -1027,7 +1060,7 @@ export default defineComponent({
 				if (abcData.abc.measures[data.active.measureIndex].notes.length === 0) {
 					abcData.abc.measures.splice(data.active.measureIndex, 1);
 				}
-				handleResetRender();
+				await handleResetRender();
 				data.active = null as unknown as INoteActive;
 			}
 		};
@@ -1179,6 +1212,10 @@ export default defineComponent({
 				value: index,
 			}));
 		});
+		const instrumentName = computed(() => {
+			const code = ABCJS.synth.instrumentIndexToName[abcData.synthOptions.program];
+			return instrumentsNames[code as keyof typeof instrumentsNames];
+		});
 		const getDetailData = async () => {
 			data.loading = true;
 			const query: any = getQuery();
@@ -1197,8 +1234,8 @@ export default defineComponent({
 					console.log("🚀 ~ abc:", abc);
 
 					abcData.abc.celf = abc.celf || "K:treble";
-					abcData.abc.key = abc.key.value || "K:C";
-					abcData.abc.meter = abc.meter.value || "M:4/4";
+					abcData.abc.key = abc.key.value || abc.key || "K:C";
+					abcData.abc.meter = abc.meter.value || abc.meter || "M:4/4";
 					abcData.abc.speed = abc.speed || "Q:1/4=60";
 					abcData.abc.visualTranspose = abc.visualTranspose || 0;
 					abcData.abc.subjectCode = abc.subjectCode || "acoustic_grand_piano";
@@ -1209,6 +1246,7 @@ export default defineComponent({
 				}
 			}
 			data.loading = false;
+			return res;
 		};
 		const handleSaveMusic = async (tips = true) => {
 			await api_musicSheetCreationUpdate({
@@ -1226,7 +1264,6 @@ export default defineComponent({
 		};
 		onMounted(async () => {
 			await getDetailData();
-			loadMiniMp3();
 			if (ABCJS.synth.supportsAudio()) {
 				abcData.synthControl = new ABCJS.synth.SynthController();
 				// console.log("🚀 ~ abcData.synthControl:", abcData.synthControl)
@@ -1240,6 +1277,7 @@ export default defineComponent({
 
 			console.log(ABCJS);
 			await handleResetRender();
+			loadMiniMp3();
 			document.addEventListener("keyup", handleKeyUp);
 			window.onbeforeunload = (e) => {
 				if (!data.isSave) {
@@ -1248,7 +1286,6 @@ export default defineComponent({
 				}
 			};
 			abcData.synthControl.restart();
-			// formateAbc(abcData.visualObj);
 			const selectMearesBtn = document.querySelector("#selectMearesBtn");
 			if (selectMearesBtn) {
 				const rect = selectMearesBtn.getBoundingClientRect();
@@ -1397,6 +1434,20 @@ export default defineComponent({
 			}
 		};
 
+		const downXML = async () => {
+			const msg = message.loading("导出中...");
+			await handleSaveMusic(false);
+			const res = await getDetailData();
+			if (!res?.data?.xml) {
+				msg.type = "error";
+				msg.content = "导出失败";
+				return;
+			}
+			saveAs(res.data.xml, (data.musicName || "曲谱") + ".xml");
+			msg.type = "success";
+			msg.content = "导出成功";
+		};
+
 		const handleDownFile = (type: IFileBtnType) => {
 			if (type === "png") {
 				downPng();
@@ -1404,6 +1455,8 @@ export default defineComponent({
 				downMidi();
 			} else if (type === "wav") {
 				downWav();
+			} else if (type === "down-xml") {
+				downXML();
 			}
 		};
 
@@ -1413,16 +1466,19 @@ export default defineComponent({
 			input.accept = ".xml,.musicxml";
 			input.onchange = (e: any) => {
 				const file = e.target.files[0];
+
 				const reader = new FileReader();
 				reader.onload = (e: any) => {
 					let abc = e.target.result;
-					console.log("🚀 ~ abc:", abc);
+					// console.log("🚀 ~ abc:", abc);
 					abc = new DOMParser().parseFromString(abc, "text/xml");
-					console.log("🚀 ~ abc:", abc);
+					// console.log("🚀 ~ abc:", abc);
 					abc = (window as any).vertaal(abc, { p: "f", t: 1, u: 0, v: 3, mnum: 0 });
-					console.log(abc);
-					// data.music = abc[0];
-					// handleResetRender(false);
+					// console.log(abc);
+					const parseData = ABCJS.renderAbc("importRef", abc[0], { responsive: "resize" });
+					console.log("🚀 ~ parseData:", parseData);
+					abcData.abc = formateAbc(parseData[0], { subjectCode: abcData.abc.subjectCode });
+					handleResetRender();
 				};
 				reader.readAsText(file);
 			};
@@ -1485,7 +1541,7 @@ export default defineComponent({
 										} else if (["xml"].includes(val)) {
 											handleExport();
 										} else if (val === "upload") {
-										} else if (["png", "midi", "wav"].includes(val)) {
+										} else if (["png", "midi", "wav", "down-xml"].includes(val)) {
 											handleDownFile(val);
 										} else if (val === "print") {
 										}
@@ -1656,7 +1712,16 @@ export default defineComponent({
 											<div class={styles.btnImg} onClick={() => (popup.instrument = true)}>
 												<img class={styles.topBtnIcon} src={getImage("icon_25.png")} />
 											</div>
-											<div>选择声部</div>
+											<div
+												style={{
+													overflow: "hidden",
+													textOverflow: "ellipsis",
+													whiteSpace: "nowrap",
+													maxWidth: "60px",
+												}}
+											>
+												{instrumentName.value}
+											</div>
 										</div>
 									),
 									default: () => (
@@ -2008,14 +2073,13 @@ export default defineComponent({
 
 							<div class={styles.topLine}></div>
 
-							<div
-								style={{ marginLeft: "auto" }}
-								class={styles.topBtn}
-								onClick={() => togglePlay("reset")}
-							>
-								<div class={styles.btnImg}>
-									<img class={styles.topBtnIcon} src={getImage("icon_20.png")} />
-								</div>
+							<div style={{ marginLeft: "auto" }} class={styles.topBtn}>
+								<NSpin show={data.loadingAudioSrouce} size="small">
+									<div class={styles.btnImg} onClick={() => togglePlay("reset")}>
+										<img class={styles.topBtnIcon} src={getImage("icon_20.png")} />
+									</div>
+								</NSpin>
+
 								<div>重播</div>
 							</div>
 							<div class={styles.topBtn}>
@@ -2049,8 +2113,14 @@ export default defineComponent({
 								<div>选段</div>
 							</div>
 
-							<div class={[styles.topBtn, styles.btnDisabled]}>
-								<div class={styles.btnImg}>
+							<div
+								class={[styles.topBtn]}
+								onClick={() => {
+									metronomeData.disable = !metronomeData.disable;
+									metronomeData.metro?.initPlayer();
+								}}
+							>
+								<div class={[styles.btnImg, !metronomeData.disable && styles.btnImgActive]}>
 									<img class={styles.topBtnIcon} src={getImage("icon_23.png")} />
 								</div>
 								<div>节拍器</div>
@@ -2284,13 +2354,8 @@ export default defineComponent({
 							)}
 
 							{/* <textarea ref={textAreaRef} class={styles.value} id="abc"></textarea> */}
+							<div id="importRef" style={{ display: "none" }}></div>
 							<div id="audio" style={{ display: "none" }}></div>
-							<div id="warnings"></div>
-
-							<p class="beat"></p>
-							<pre class="clicked-info"></pre>
-							<pre class="feedback"></pre>
-							<div id="container"></div>
 							{data.loadingAudioSrouce && (
 								<div class={styles.loading}>
 									<NSpin></NSpin>
@@ -2323,7 +2388,7 @@ export default defineComponent({
 									<div class={styles.mearesInput}>
 										<NInputNumber
 											min={1}
-											max={data.selectMeasures.max}
+											max={data.selectMeasures.end || data.selectMeasures.max}
 											bordered={false}
 											placeholder="开始小节"
 											showButton={false}

+ 72 - 0
src/pc/home/noteData.ts

@@ -28,6 +28,78 @@ export const ALL_NOTES = () => {
 	// console.log(notes);
 	return str;
 };
+export const ALL_Pitches = [
+	"C,,,,",
+	"D,,,,",
+	"E,,,,",
+	"F,,,,",
+	"G,,,,",
+	"A,,,,",
+	"B,,,,",
+	"C,,,",
+	"D,,,",
+	"E,,,",
+	"F,,,",
+	"G,,,",
+	"A,,,",
+	"B,,,",
+	"C,,",
+	"D,,",
+	"E,,",
+	"F,,",
+	"G,,",
+	"A,,",
+	"B,,",
+	"C,",
+	"D,",
+	"E,",
+	"F,",
+	"G,",
+	"A,",
+	"B,",
+	"C",
+	"D",
+	"E",
+	"F",
+	"G",
+	"A",
+	"B",
+	"c",
+	"d",
+	"e",
+	"f",
+	"g",
+	"a",
+	"b",
+	"c'",
+	"d'",
+	"e'",
+	"f'",
+	"g'",
+	"a'",
+	"b'",
+	"c''",
+	"d''",
+	"e''",
+	"f''",
+	"g''",
+	"a''",
+	"b''",
+	"c'''",
+	"d'''",
+	"e'''",
+	"f'''",
+	"g'''",
+	"a'''",
+	"b'''",
+	"c''''",
+	"d''''",
+	"e''''",
+	"f''''",
+	"g''''",
+	"a''''",
+	"b''''",
+];
 export const ABC_NOTE_DATA = [
 	"C,,,,",
 	["^C,,,,", "_D,,,,"],

+ 115 - 26
src/pc/home/runtime.ts

@@ -185,10 +185,10 @@ interface IRenderMeasuresOption {
 	showTitle?: boolean;
 	showCreator?: boolean;
 }
-/** 
+/**
  * 生成小节
  * @param abc
- * 
+ *
  * @returns
  */
 export const renderMeasures = (abc: IAbc, option?: IRenderMeasuresOption) => {
@@ -196,7 +196,7 @@ export const renderMeasures = (abc: IAbc, option?: IRenderMeasuresOption) => {
 	let wrap = 1;
 	let text = `X:1\n`;
 
-	if (option?.showTitle){
+	if (option?.showTitle) {
 		abc.title && (text += abc.title + "\n");
 	}
 	if (option?.showCreator) {
@@ -211,43 +211,43 @@ export const renderMeasures = (abc: IAbc, option?: IRenderMeasuresOption) => {
 	const measures = abc.measures;
 	for (let i = 0; i < measures.length; i++) {
 		const measure = measures[i];
-		text += measure.repeat ?? '';// 重复
-		text += measure.meter ?? '';// 拍号
+		text += measure.repeat ?? ""; // 重复
+		text += measure.meter ?? ""; // 拍号
 		for (let j = 0; j < measure.notes.length; j++) {
 			const note = measure.notes[j];
 			const playStr = note.play?.join("") ?? "";
 
-			text += note.clef ?? ''; // 谱号
-			text += note.key ?? ''; // 调号
-			text += note.speed ?? ''; // 速度
-			text += note.slus ?? ''; // 3连音
+			text += note.clef ?? ""; // 谱号
+			text += note.key ?? ""; // 调号
+			text += note.speed ?? ""; // 速度
+			text += note.slus ?? ""; // 3连音
 			if (note.tie?.includes("(")) {
 				// 连音线 前
-				text += note.tie ?? '';
+				text += note.tie ?? "";
 			}
-			if (!option?.hiddenIndex){
+			if (!option?.hiddenIndex) {
 				text += `"<${i + "." + j}"`; // 音符 id
 			}
-			text += playStr ?? ''; // 演奏技法
-			text += note.dynamics ?? ''; // 力度符号
-			text += note.accidental ?? ''; // 临时升降记号
-			text += note.content ?? ''; // 音符
+			text += playStr ?? ""; // 演奏技法
+			text += note.dynamics ?? ""; // 力度符号
+			text += note.accidental ?? ""; // 临时升降记号
+			text += note.content ?? ""; // 音符
 			// 音符时值
-			text += note.noteType ?? '';
-			text += note.dot ?? ''; // 点
+			text += note.noteType ?? "";
+			text += note.dot ?? ""; // 点
 
-			text += note.tieline ?? ''; // 延音
+			text += note.tieline ?? ""; // 延音
 			if (note.tie?.includes(")")) {
 				// 连音线 后
-				text += note.tie ?? '';
+				text += note.tie ?? "";
 			}
-			text += note.segno ?? ''; // 分割
+			text += note.segno ?? ""; // 分割
 		}
 		let _i = i + 1;
 		if (!option?.hiddenIndex) {
-			text += `"<${_i}"`
+			text += `"<${_i}"`;
 		}
-		text += measure.barline ?? '';
+		text += measure.barline ?? "";
 		if (wrap % 4 === 0) {
 			text += "\n";
 		}
@@ -294,7 +294,23 @@ export const moveNoteKey = (note: string, moveData: { step: number; move: number
 	return note;
 };
 
-export const formateAbc = (visualObj: TuneObject) => {
+const formateGetData = {
+	getNoteType: (duration: number) => {
+		const type = 0.25 / duration;
+		console.log(type);
+		const noteType = [
+			{ name: 0.25, value: "4" },
+			{ name: 0.5, value: "2" },
+			{ name: 1, value: "" },
+			{ name: 2, value: "/" },
+			{ name: 4, value: "//" },
+			{ name: 8, value: "///" },
+		];
+		return noteType.find((n) => n.name === type)?.value || "";
+	},
+};
+
+export const formateAbc = (visualObj: TuneObject, option: any) => {
 	const abc = {
 		celf: "K:treble",
 		minUnit: "L:1/4",
@@ -302,10 +318,32 @@ export const formateAbc = (visualObj: TuneObject) => {
 		speed: "Q:1/4=60",
 		key: "K:C",
 		visualTranspose: 0,
-		subjectCode: "acoustic_grand_piano",
+		subjectCode: option.subjectCode ?? "acoustic_grand_piano",
 	};
 	const list = [];
+	let notes = [];
 	let measureIndex = 0;
+
+	const get_accidental = (noteItem: INote) => {
+		let accidental = "";
+
+		if (noteItem.content.includes("_")) {
+			accidental = "_";
+		}
+		if (noteItem.content.includes("__")) {
+			accidental = "__";
+		}
+		if (noteItem.content.includes("=")) {
+			accidental = "=";
+		}
+		if (noteItem.content.includes("^")) {
+			accidental = "^";
+		}
+		if (noteItem.content.includes("^^")) {
+			accidental = "^^";
+		}
+		return accidental;
+	};
 	for (let i = 0; i < visualObj.lines.length; i++) {
 		const line = visualObj.lines[i];
 		if (line.staff) {
@@ -323,15 +361,62 @@ export const formateAbc = (visualObj: TuneObject) => {
 					}
 				}
 				if (staff.voices) {
+					let measure = {
+						notes: [] as INote[],
+						barline: "|",
+						repeat: "",
+						measureNumber: measureIndex,
+						celf: "",
+						key: "",
+						meter: "",
+					};
 					for (let k = 0; k < staff.voices.length; k++) {
 						const voice = staff.voices[k];
 						for (let l = 0; l < voice.length; l++) {
 							const element = voice[l];
 							if (element.el_type === "bar") {
 								measureIndex++;
+								list.push(measure);
+								notes = [];
+								measure = {
+									notes: [] as INote[],
+									barline: "|",
+									repeat: "",
+									measureNumber: measureIndex,
+									celf: "",
+									key: "",
+									meter: "",
+								};
 							}
 							if (element.el_type === "note") {
-								list.push(element);
+								// const abcEle = visualObj.getElementFromChar(element.startChar);
+								// console.log("🚀 ~ abcEle:", element);
+								let noteItem = {
+									clef: "", //// 谱号
+									key: "", // 调号
+									speed: "", // 速度
+									slus: "", // 3连音
+									tie: "", // 连音线 前,连音线 后
+									content: "", // 音符
+									noteType: formateGetData.getNoteType(element.duration), // 音符时值
+									play: [],
+									dynamics: "", // 力度符号
+									accidental: "", // 临时升降记号
+									dot: "", //附点
+									tieline: "", // 延音线
+									segno: "", // 分割
+								};
+								if (element.rest) {
+									noteItem.content = "z";
+								} else {
+									noteItem.content = element.pitches?.[0]?.name ?? "";
+								}
+								noteItem.accidental = get_accidental(noteItem as any);
+								if (noteItem.accidental) {
+									noteItem.content = noteItem.content.replace(noteItem.accidental, "");
+								}
+								const note = createNote(noteItem);
+								measure.notes.push(note);
 							}
 						}
 					}
@@ -339,5 +424,9 @@ export const formateAbc = (visualObj: TuneObject) => {
 			}
 		}
 	}
-	console.log(measureIndex);
+	console.log(measureIndex, list);
+	return {
+		...abc,
+		measures: list,
+	};
 };