Jelajahi Sumber

Merge branch 'iteration-1.0.1' into online

liushengqiang 1 tahun lalu
induk
melakukan
5e5aa3a78a

+ 2 - 0
package.json

@@ -14,6 +14,8 @@
     "@varlet/ui": "^2.13.4",
     "@vicons/fa": "^0.12.0",
     "@vicons/ionicons5": "^0.12.0",
+    "@vueuse/components": "^10.3.0",
+    "@vueuse/core": "^10.3.0",
     "abcjs": "^6.2.2",
     "clean-deep": "^3.4.0",
     "consola": "^2.15.3",

+ 59 - 2
pnpm-lock.yaml

@@ -19,6 +19,12 @@ dependencies:
   '@vicons/ionicons5':
     specifier: ^0.12.0
     version: 0.12.0
+  '@vueuse/components':
+    specifier: ^10.3.0
+    version: 10.3.0(vue@3.2.47)
+  '@vueuse/core':
+    specifier: ^10.3.0
+    version: 10.3.0(vue@3.2.47)
   abcjs:
     specifier: ^6.2.2
     version: 6.2.2
@@ -119,7 +125,7 @@ devDependencies:
     version: 4.9.3
   unplugin-auto-import:
     specifier: ^0.16.6
-    version: 0.16.6
+    version: 0.16.6(@vueuse/core@10.3.0)
   unplugin-vue-components:
     specifier: ^0.24.1
     version: 0.24.1(vue@3.2.47)
@@ -1853,6 +1859,9 @@ packages:
     resolution: {integrity: sha512-ZPHnXkzmGMfk+pHqAGzTSpA9CbsHmJLgkvOl5w52LZ0XTxB1ZIHWZzQ7lEtjTNWScBbsQekg8TjApMXkMe4nkw==}
     dev: true
 
+  /@types/web-bluetooth@0.0.17:
+    resolution: {integrity: sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA==}
+
   /@vant/popperjs@1.3.0:
     resolution: {integrity: sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==}
     dev: false
@@ -2155,6 +2164,39 @@ packages:
     resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==}
     dev: true
 
+  /@vueuse/components@10.3.0(vue@3.2.47):
+    resolution: {integrity: sha512-EeZz3kjmJI7bH7JSxxMlLyk21LGl6GQjXfpl2n/GiI9QSJi+BVzIra5kEty5eM8McwAanx3e/HnK4drYTgFOWw==}
+    dependencies:
+      '@vueuse/core': 10.3.0(vue@3.2.47)
+      '@vueuse/shared': 10.3.0(vue@3.2.47)
+      vue-demi: 0.14.5(vue@3.2.47)
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
+    dev: false
+
+  /@vueuse/core@10.3.0(vue@3.2.47):
+    resolution: {integrity: sha512-BEM5yxcFKb5btFjTSAFjTu5jmwoW66fyV9uJIP4wUXXU8aR5Hl44gndaaXp7dC5HSObmgbnR2RN+Un1p68Mf5Q==}
+    dependencies:
+      '@types/web-bluetooth': 0.0.17
+      '@vueuse/metadata': 10.3.0
+      '@vueuse/shared': 10.3.0(vue@3.2.47)
+      vue-demi: 0.14.5(vue@3.2.47)
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
+
+  /@vueuse/metadata@10.3.0:
+    resolution: {integrity: sha512-Ema3YhNOa4swDsV0V7CEY5JXvK19JI/o1szFO1iWxdFg3vhdFtCtSTP26PCvbUpnUtNHBY2wx5y3WDXND5Pvnw==}
+
+  /@vueuse/shared@10.3.0(vue@3.2.47):
+    resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==}
+    dependencies:
+      vue-demi: 0.14.5(vue@3.2.47)
+    transitivePeerDependencies:
+      - '@vue/composition-api'
+      - vue
+
   /abcjs@6.2.2:
     resolution: {integrity: sha512-M5pvTb+0yABqScZBEN/bZmrMgX+iTtgD8vxAVTaKNNVt+ivyCzx0SLNYlTXYErlXu7QvPIoIiSPRojne6M24PA==}
     dev: false
@@ -3703,7 +3745,7 @@ packages:
     engines: {node: '>= 10.0.0'}
     dev: true
 
-  /unplugin-auto-import@0.16.6:
+  /unplugin-auto-import@0.16.6(@vueuse/core@10.3.0):
     resolution: {integrity: sha512-M+YIITkx3C/Hg38hp8HmswP5mShUUyJOzpifv7RTlAbeFlO2Tyw0pwrogSSxnipHDPTtI8VHFBpkYkNKzYSuyA==}
     engines: {node: '>=14'}
     peerDependencies:
@@ -3717,6 +3759,7 @@ packages:
     dependencies:
       '@antfu/utils': 0.7.5
       '@rollup/pluginutils': 5.0.2
+      '@vueuse/core': 10.3.0(vue@3.2.47)
       fast-glob: 3.3.0
       local-pkg: 0.4.3
       magic-string: 0.30.1
@@ -3904,6 +3947,20 @@ packages:
       vue: 3.2.47
     dev: false
 
+  /vue-demi@0.14.5(vue@3.2.47):
+    resolution: {integrity: sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==}
+    engines: {node: '>=12'}
+    hasBin: true
+    requiresBuild: true
+    peerDependencies:
+      '@vue/composition-api': ^1.0.0-rc.1
+      vue: ^3.0.0-0 || ^2.6.0
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+    dependencies:
+      vue: 3.2.47
+
   /vue-router@4.1.6(vue@3.2.47):
     resolution: {integrity: sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==}
     peerDependencies:

+ 0 - 21
src/pc/component/keys/index.tsx

@@ -30,7 +30,6 @@ export default defineComponent({
 		};
 
 		const keyDown = (e: KeyboardEvent) => {
-			console.log("🚀 ~ e:", e);
 			data.keyDown = e.key.toLocaleUpperCase();
 		};
 		const keyUp = (e: KeyboardEvent) => {
@@ -94,26 +93,6 @@ export default defineComponent({
 									</div>
 								);
 							})}
-							{/* <div
-								class={styles.bkey}
-								onClick={() => {
-									// emit("click", { type: "note", value: item.key + productKey(index - 3) });
-								}}
-							>
-								<div class="keytip"></div>
-							</div>
-							<div class={styles.bkey}>
-								<div class="keytip"></div>
-							</div>
-							<div class={styles.bkey}>
-								<div class="keytip"></div>
-							</div>
-							<div class={styles.bkey}>
-								<div class="keytip"></div>
-							</div>
-							<div class={styles.bkey}>
-								<div class="keytip"></div>
-							</div> */}
 						</div>
 					</div>
 				))}

+ 12 - 2
src/pc/home/component/file-btn/index.tsx

@@ -4,8 +4,8 @@ import styles from "./index.module.less";
 import { getImage } from "../../images";
 import { DropdownMixedOption } from "naive-ui/es/dropdown/src/interface";
 
-/** 新建 | 保存 | 导入 | 上传 | 导出 | 打印 */
-export type IFileBtnType = "newMusic" | "save" | "xml" | "upload" | "png" | "wav" | "midi" | "print";
+/** 新建 | 保存 | 导入 | 上传 | 导出 | 打印 | 退出*/
+export type IFileBtnType = "newMusic" | "save" | "xml" | "upload" | "png" | "wav" | "midi" | "print" | 'exit';
 
 export default defineComponent({
 	name: "FileBtn",
@@ -90,6 +90,16 @@ export default defineComponent({
 				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

TEMPAT SAMPAH
src/pc/home/images/icon_26_5.png


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

@@ -23,6 +23,13 @@
     flex-wrap: wrap;
     padding-left: 20px;
     width: 100%;
+
+    .topBtn {
+        .btnImg {
+            width: 40px;
+            height: 40px;
+        }
+    }
 }
 
 .topBtn {
@@ -49,21 +56,13 @@
         }
     }
 
-
-
-    :global {
-        .arco-dropdown-open {
-            background-color: rgba(193, 219, 251, 1) !important;
-        }
-    }
-
     .btnImg.btnImgActive {
         background-color: rgba(193, 219, 251, 1);
     }
 
     .topBtnIcon {
-        width: 42px;
-        height: 42px;
+        width: 100%;
+        height: 100%;
     }
 }
 
@@ -174,6 +173,21 @@
             cursor: pointer;
         }
     }
+    .abcjs-bar{
+        .abcjs-annotation{
+            display: block;
+            font-size: 12px;
+            font-style: italic;
+            transform: translateX(10px);
+        }
+    }
+
+    
+
+    .ABCJS-cursor {
+        background: rgba(25, 140, 254, 0.6);
+        border-radius: 5px;
+    }
 }
 
 .instruments {
@@ -255,13 +269,45 @@
         }
     }
 }
-.titleName{
-    :global{
-        .n-input{
+
+.titleName {
+    :global {
+        .n-input {
             font-size: 18px;
             font-weight: 600;
             height: 40px;
             line-height: 40px;
         }
     }
+}
+
+.selectMearesBox {
+    width: 286px;
+    position: fixed;
+    padding: 12px !important;
+    background: #FFFFFF;
+    box-shadow: 0px 2px 17px 0px rgba(0, 0, 0, 0.08);
+    border-radius: 16px;
+    border: 1px solid #F5F5F7;
+    cursor: move;
+
+    .mearesInput {
+        display: flex;
+        align-items: center;
+        flex: 1;
+        border: 1px solid rgb(224, 224, 230);
+        border-radius: 4px;
+
+        :global {
+
+            .n-input__placeholder,
+            .n-input__input-el {
+                text-align: center;
+            }
+        }
+    }
+}
+
+.selectMearesHidden {
+    display: none;
 }

+ 298 - 142
src/pc/home/index.tsx

@@ -25,7 +25,6 @@ import TheIcon from "/src/components/The-icon";
 import { cloneDeep } from "lodash";
 import TheSpeed from "./component/the-speed";
 import { getImage } from "./images";
-import { Dropdown, Dsubmenu, Doption, Trigger, Input, Select, Option } from "@arco-design/web-vue";
 import {
 	NButton,
 	NDropdown,
@@ -50,6 +49,9 @@ 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 { Close } from "@vicons/ionicons5";
+import { UseDraggable } from "@vueuse/components";
 
 const allPitches = [
 	"C,,,,",
@@ -185,6 +187,7 @@ export default defineComponent({
 			mearseDeleteShow: false,
 			staffShow: false, // 五线谱弹窗
 			settingShow: false, // 设置弹窗
+			selectMearesShow: false, // 选择小节弹窗
 		});
 		const data = reactive({
 			drawCount: 0,
@@ -205,9 +208,16 @@ export default defineComponent({
 			isClickNote: false,
 			/** 音符类型 */
 			noteType: "",
-			selectMeasure: {
-				start: "",
-				end: "",
+			/** 选择小节范围 */
+			selectMeasures: {
+				state: false,
+				x: 0,
+				y: 0,
+				start: 1,
+				startNote: null as any,
+				end: 0,
+				endNote: null as any,
+				max: 30,
 			},
 			slide: ["note", "meter", "dynamics"],
 
@@ -274,7 +284,7 @@ export default defineComponent({
 				const totalTime = (abcData.synthControl as any).visualObj.getTotalTime();
 				if (totalTime) {
 					const progress = (data.active as any).currentTrackMilliseconds / 1000 / totalTime;
-					// console.log("🚀 ~ data.active:", data.active, progress);
+					// console.log("🚀 ~ data.active:", progress);
 					(abcData.synthControl as any).seek(progress);
 				}
 			}
@@ -282,7 +292,7 @@ export default defineComponent({
 			// 	abcData.visualObj.engraver.rangeHighlight(abcElem.startChar, abcElem.endChar);
 			// }
 			if (drag && drag.step) {
-				handleMoveNote("drag", drag.step);
+				handleChange({ type: "move", value: { action: "drag", step: drag.step } });
 				return;
 			}
 			if (!abcElem?.midiPitches) return;
@@ -295,6 +305,7 @@ export default defineComponent({
 			midiBuffer: null as unknown as ABCJS.MidiBuffer,
 			abcOptions: {
 				selectionColor: "#0f81ff",
+				jazzchords: true,
 				add_classes: true,
 				clickListener: clickListener,
 				responsive: "resize",
@@ -302,8 +313,8 @@ export default defineComponent({
 				selectTypes: ["note"],
 				visualTranspose: 0,
 				wrap: {
-					minSpacing: 2,
-					maxSpacing: 10,
+					minSpacing: 1.8,
+					maxSpacing: 2.7,
 					preferredMeasuresPerLine: 4,
 				},
 				staffwidth: 800,
@@ -335,27 +346,6 @@ export default defineComponent({
 			}
 		};
 
-		/**
-		 * 分词
-		 * @param str 字符串
-		 * @returns
-		 * @description
-		 */
-		const tokenize = (str: string) => {
-			const arr = str.split(/(!.+?!|".+?")/);
-			let output: string[] = [];
-			for (let i = 0; i < arr.length; i++) {
-				const token = arr[i];
-				if (token.length > 0) {
-					if (token[0] !== '"' && token[0] !== "!") {
-						const arr2 = arr[i].split(/([A-Ga-gz][,']*)/);
-						output = output.concat(arr2);
-					} else output.push(token);
-				}
-			}
-			return output;
-		};
-
 		const hideCursor = () => {
 			const cursor = document.querySelector("#paper svg .ABCJS-cursor");
 			if (cursor) {
@@ -367,13 +357,7 @@ export default defineComponent({
 		};
 
 		const cursorControl = {
-			// self.onReady = function () {
-			// 	var downloadLink = document.querySelector(".download");
-			// 	downloadLink.addEventListener("click", download);
-			// 	downloadLink.setAttribute("style", "");
-			// 	var clickEl = document.querySelector(".click-explanation");
-			// 	clickEl.setAttribute("style", "");
-			// };
+			onReady: function () {},
 			onStart: function () {
 				console.log("开始");
 				data.playState = true;
@@ -386,12 +370,28 @@ export default defineComponent({
 				cursor.setAttributeNS(null, "y2", "0");
 				svg?.appendChild(cursor);
 			},
-			// self.beatSubdivisions = 2;
 			onBeat: function (beatNumber: any, totalBeats: any, totalTime: any) {},
 			onEvent: (ev: any) => {
+				// console.log("🚀 ~ ev:", ev);
 				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 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)
+					) {
+						const totalTime = (abcData.synthControl as any).visualObj.getTotalTime();
+						if (totalTime) {
+							const progress = startTime / 1000 / totalTime;
+							nextTick(() => {
+								(abcData.synthControl as any).seek(progress);
+							});
+						}
+					}
+				}
 				var cursor = document.querySelector("#paper svg .ABCJS-cursor");
 				if (cursor) {
 					cursor.setAttribute("x1", ev.left + ev.width / 2);
@@ -411,14 +411,24 @@ export default defineComponent({
 			},
 		};
 
-		const resetMidi = (useActive = false) => {
+		const staffNotes = ALL_NOTES();
+		const loadMiniMp3 = async () => {
 			data.loadingAudioSrouce = true;
 			const midiBuffer = new ABCJS.synth.CreateSynth();
-			// console.log(midiBuffer);
+			const str = `X: 1\nM:4/4\nL:1/4\n${staffNotes}`;
+			const visualObj = ABCJS.parseOnly(str);
+			await midiBuffer.init({
+				visualObj: visualObj[0],
+				options: { ...abcData.synthOptions },
+			});
+		};
+
+		const resetMidi = (useActive = false) => {
+			const midiBuffer = new ABCJS.synth.CreateSynth();
 			midiBuffer
 				.init({
 					visualObj: abcData.visualObj,
-					options: { ...abcData.synthOptions, midiTranspose: abcData.abc.visualTranspose },
+					options: { ...abcData.synthOptions },
 				})
 				.then(() => {
 					abcData.synthControl
@@ -428,7 +438,7 @@ export default defineComponent({
 						})
 						.then(function (response) {
 							data.loadingAudioSrouce = false;
-							// console.log("Audio successfully loaded.");
+							// console.log("Audio successfully loaded.", {...abcData.synthControl});
 							// console.log("🚀 ~ abcData.synthControl:", abcData.synthControl);
 						})
 						.catch((err) => {
@@ -438,7 +448,6 @@ export default defineComponent({
 		};
 
 		const togglePlay = (type: "play" | "pause" | "reset") => {
-			console.log("🚀 ~ abcData.synthControl:", abcData.synthControl);
 			if (type === "play") {
 				abcData.synthControl.play();
 				data.playState = true;
@@ -449,6 +458,7 @@ export default defineComponent({
 			} else {
 				abcData.synthControl.restart();
 			}
+			// console.log("🚀 ~ abcData.synthControl:", abcData.synthControl.timer.noteTimings);
 		};
 
 		const renderSvg = () => {
@@ -456,19 +466,26 @@ export default defineComponent({
 				...abcData.abcOptions,
 				visualTranspose: abcData.abc.visualTranspose,
 			})[0];
-			console.log("🚀 ~ visualObj:", abcData.visualObj);
+			if (data.drawCount < 3) {
+				console.log("🚀 ~ visualObj:", abcData.visualObj);
+			}
 		};
 
 		const renderBoxRect = () => {
 			const svg = document.querySelector("#paper svg");
 			const padding = 4;
+			let measureNumber = 0;
 			for (let i = 0; i < abcData.visualObj.lines.length; i++) {
 				const line = abcData.visualObj.lines[i];
+				// console.log("🚀 ~ line:", line);
 				for (let j = 0; j < line.staff.length; j++) {
 					const staff = line.staff[j];
 					const voices = [...staff.voices].flat();
 					for (let l = 0; l < voices.length; l++) {
 						const item = voices[l];
+						if (item.el_type === "bar") {
+							measureNumber++;
+						}
 						// console.log(item.el_type);
 						if (["note", "keySignature", "clef", "timeSignature"].includes(item.el_type)) {
 							const box = item.abselem.elemset?.[0]?.getBBox?.() || null;
@@ -489,25 +506,26 @@ export default defineComponent({
 					}
 				}
 			}
-			// const annotation = document.querySelectorAll("#paper .abcjs-annotation");
-			// annotation.forEach((n) => {
-			// 	n.setAttribute("color", "rgba(0,0,0,0)");
-			// })
+			console.log(measureNumber);
+			data.selectMeasures.max = measureNumber;
 		};
 
 		/**
 		 * @param isProduct 是否是生成曲谱
 		 */
-		const handleResetRender = (isProduct = true) => {
+		const handleResetRender = () => {
 			return new Promise((resolve) => {
 				nextTick(() => {
-					data.music = isProduct ? renderMeasures(abcData.abc) : data.music;
+					data.music = renderMeasures(abcData.abc);
 					renderSvg();
 					resetMidi(data.drawCount > 0 ? true : false);
 					renderBoxRect();
 					resolve(1);
 					textAreaRef.value && (textAreaRef.value.value = data.music);
 					data.drawCount++;
+
+					// const times = new ABCJS.TimingCallbacks(abcData.visualObj);
+					// console.log("🚀 ~ times:", times)
 				});
 			});
 		};
@@ -546,11 +564,15 @@ export default defineComponent({
 		 * @returns
 		 */
 		const handleChange = async (params: { type: string; value: any }) => {
+			abcData.synthControl.disable(true);
+			if (data.playState) {
+				data.playState = false;
+			}
 			const type = params.type;
 			const value = params.value;
 			const activeNote =
 				abcData.abc.measures[data.active?.measureIndex]?.notes[data.active?.noteIndex] || null;
-			console.log(params, activeNote);
+			// console.log(params, activeNote);
 			if (type === "type") {
 				// 设置音符类型
 				data.noteType = value;
@@ -595,59 +617,78 @@ export default defineComponent({
 						if (_values[1]) {
 							activeNote.accidental = _values[1] || "";
 						}
+						data.active.isFirstChecked = false;
+					}
+					await handleResetRender();
+					const oldNote = useIndexGetNote(`${data.active.measureIndex}.${data.active.noteIndex}`);
+					if (oldNote?.abselem?.beam?.elems?.length) {
+						// 判断是否需要分割beam
+						const elems: AbcElem[] = oldNote.abselem.beam.elems;
+						const beatDuration = abcData.visualObj.getBeatLength();
+						const beamLength = elems.map((n) => n.duration).reduce((a, b) => a + b);
+						if (beamLength >= beatDuration) {
+							abcData.abc.measures[data.active.measureIndex].notes[data.active.noteIndex].segno = " ";
+							await handleResetRender();
+						}
+					}
+					if (oldNote?.midiPitches) {
+						ABCJS.synth.playEvent(oldNote.midiPitches, oldNote.midiGraceNotePitches, 1000);
+					}
+					const nextNote =
+						abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex + 1];
+					if (nextNote) {
+						const abcNextElem: AbcElem = useIndexGetNote(
+							`${data.active.measureIndex}.${data.active.noteIndex + 1}`
+						);
+						rangeHighlight(abcNextElem.startChar);
+						data.active = {
+							...abcNextElem,
+							measureIndex: data.active.measureIndex,
+							noteIndex: data.active.noteIndex + 1,
+							isFirstChecked: true,
+						};
 					} else {
 						const notes = getMeasureNotes(data.active.measureIndex);
 						const duration = notes.map((n) => n.duration).reduce((a, b) => a + b);
 						if (duration >= 1) {
-							message.warning("小节内音符总时值过长");
-							return;
-						}
-
-						handleCreateNote(
-							data.active.measureIndex,
-							data.active.noteIndex,
-							createNote({
-								content: _values[0],
-								noteType: data.noteType,
-								accidental: _values[1] || "",
-							})
-						);
-					}
-					await handleResetRender();
-					let _abcElem: AbcElem;
-					if (data.active.isFirstChecked) {
-						data.active.isFirstChecked = false;
-						_abcElem = rangeHighlight(data.active.startChar);
-					} else {
-						const oldElem: AbcElem = abcData.visualObj.getElementFromChar(data.active.startChar);
-						const abcElem: AbcElem = abcData.visualObj.getElementFromChar(oldElem.endChar);
-						if (abcElem) {
-							let indexStr: any = abcElem.chord?.find((n) => n.position === "left")?.name || "";
-							indexStr = indexStr.split(".").map((n: string) => Number(n));
+							// 小节内音符总时值过长,自动跳转到下一小节
+							const nextMeasureNote = abcData.abc.measures[data.active.measureIndex + 1]?.notes[0];
+							if (nextMeasureNote) {
+								const abcNextElem: AbcElem = useIndexGetNote(`${data.active.measureIndex + 1}.${0}`);
+								rangeHighlight(abcNextElem.startChar);
+								data.active = {
+									...abcNextElem,
+									measureIndex: data.active.measureIndex + 1,
+									noteIndex: 0,
+									isFirstChecked: true,
+								};
+							} else {
+								// 到最后一个小节的最后一个音符了
+								rangeHighlight(data.active.startChar);
+								data.active.isFirstChecked = true;
+							}
+						} else {
+							handleCreateNote(
+								data.active.measureIndex,
+								data.active.noteIndex,
+								createNote({
+									content: "z",
+									noteType: data.noteType,
+								})
+							);
+							await handleResetRender();
+							const newNote = useIndexGetNote(
+								`${data.active.measureIndex}.${data.active.noteIndex + 1}`
+							);
+							rangeHighlight(newNote.startChar);
 							data.active = {
-								...abcElem,
-								measureIndex: indexStr[0],
-								noteIndex: indexStr[1],
-								isFirstChecked: false,
+								...newNote,
+								measureIndex: data.active.measureIndex,
+								noteIndex: data.active.noteIndex + 1,
+								isFirstChecked: true,
 							};
-							const beam = (abcElem.abselem as any).beam;
-							if (beam) {
-								const elems: AbcElem[] = beam.elems;
-								if (elems.length) {
-									const beatDuration = abcData.visualObj.getBeatLength();
-									const beamLength = elems.map((n) => n.duration).reduce((a, b) => a + b);
-									if (beamLength >= beatDuration) {
-										abcData.abc.measures[data.active.measureIndex].notes[data.active.noteIndex].segno =
-											" ";
-										await handleResetRender();
-									}
-								}
-							}
 						}
-						_abcElem = rangeHighlight(abcElem.startChar);
 					}
-					if (!_abcElem?.midiPitches) return;
-					ABCJS.synth.playEvent(_abcElem.midiPitches, _abcElem.midiGraceNotePitches, 1000);
 				}
 			}
 
@@ -705,6 +746,8 @@ export default defineComponent({
 					if (!activeNote) return;
 					activeNote.meter = `[${value}]`;
 					await handleResetRender();
+					const oldNote = useIndexGetNote(`${data.active.measureIndex}.${data.active.noteIndex}`);
+					rangeHighlight(oldNote.startChar);
 				} else {
 					abcData.abc.meter = value;
 					await handleResetRender();
@@ -870,10 +913,9 @@ export default defineComponent({
 					showToast("请先选择音符");
 					return;
 				}
-				const activeNote =
-					abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
 				if (!activeNote) return;
 				activeNote.dot = activeNote.dot ? "" : value;
+				// activeNote.dot = activeNote.dot ? "" : NOTE_DOT[activeNote.noteType];
 				await handleResetRender();
 				rangeHighlight(data.active.startChar);
 			}
@@ -887,6 +929,31 @@ export default defineComponent({
 				await handleResetRender();
 				rangeHighlight(data.active.startChar);
 			}
+
+			// 移动音符
+			if (type === "move") {
+				const step = value._step ? 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("").
+
+				await handleResetRender();
+				const _abcElem = rangeHighlight(data.active.startChar);
+				if (!_abcElem?.midiPitches) return;
+				ABCJS.synth.playEvent(_abcElem.midiPitches, _abcElem.midiGraceNotePitches, 1000);
+			}
+
+			// 删除音符
+			if (type === "delete") {
+				if (!data.active) return;
+				if (data.active.startChar === 0) return;
+				abcData.abc.measures[data.active.measureIndex].notes.splice(data.active.noteIndex, 1);
+				if (abcData.abc.measures[data.active.measureIndex].notes.length === 0) {
+					abcData.abc.measures.splice(data.active.measureIndex, 1);
+				}
+				handleResetRender();
+				data.active = null as unknown as INoteActive;
+			}
 		};
 
 		const getNextNote = (index: number): AbcElem => {
@@ -1007,38 +1074,19 @@ export default defineComponent({
 			await handleResetRender();
 		};
 
-		/**
-		 * 移动音符
-		 * @param note 音符
-		 * @param step 移动步数
-		 */
-		const handleMoveNote = async (type: "up" | "donw" | "drag", _step?: number) => {
-			if (!data.active) return;
-			const step = _step ? _step : type === "up" ? -1 : 1;
-			const activeNote =
-				abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
-			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("").
-
-			await handleResetRender();
-			const _abcElem = rangeHighlight(data.active.startChar);
-			if (!_abcElem?.midiPitches) return;
-			console.log(_abcElem, abcData.visualObj.millisecondsPerMeasure());
-			ABCJS.synth.playEvent(_abcElem.midiPitches, _abcElem.midiGraceNotePitches, 1000);
-		};
-
 		const handleKeyUp = (e: KeyboardEvent) => {
 			if (!data.active) return false;
 			console.log(e.key);
-			if (e.key === "Backspace") handleDeleteNote();
+			if (e.key === "Backspace") {
+				handleChange({ type: "delete", value: "" });
+			}
 			if (/^[A-Ga-g]$/.test(e.key)) {
 				handleChange({ type: "note", value: e.key.toLocaleUpperCase() });
 			}
 			if (["ArrowUp", "ArrowDown"].includes(e.key)) {
 				e.preventDefault();
 				e.stopPropagation();
-				handleMoveNote(e.key === "ArrowUp" ? "up" : "donw");
+				handleChange({ type: "move", value: { action: e.key === "ArrowUp" ? "up" : "donw" } });
 				return false;
 			}
 		};
@@ -1079,7 +1127,7 @@ export default defineComponent({
 					const _instruments = ABCJS.synth.instrumentIndexToName.indexOf(abcData.abc.subjectCode as any);
 					abcData.synthOptions.program = _instruments > -1 ? _instruments : 0;
 					abcData.abc.measures = abc.measures || initMusic(30);
-					console.log("🚀 ~ abcData.abc:", abcData.abc);
+					// console.log("🚀 ~ abcData.abc:", abcData.abc);
 				}
 			}
 		};
@@ -1097,8 +1145,10 @@ export default defineComponent({
 		};
 		onMounted(async () => {
 			await getDetailData();
+			loadMiniMp3();
 			if (ABCJS.synth.supportsAudio()) {
 				abcData.synthControl = new ABCJS.synth.SynthController();
+				// console.log("🚀 ~ abcData.synthControl:", abcData.synthControl)
 				abcData.synthControl.load("#audio", cursorControl, {
 					displayLoop: true,
 					displayRestart: true,
@@ -1116,8 +1166,15 @@ export default defineComponent({
 					e.returnValue = "还有没保存的";
 				}
 			};
-
-			formateAbc(abcData.visualObj);
+			abcData.synthControl.restart();
+			// formateAbc(abcData.visualObj);
+			const selectMearesBtn = document.querySelector("#selectMearesBtn");
+			if (selectMearesBtn) {
+				const rect = selectMearesBtn.getBoundingClientRect();
+				data.selectMeasures.x = document.body.clientWidth - 320;
+				data.selectMeasures.y = rect.top + 70;
+				data.selectMeasures.state = true;
+			}
 		});
 		onUnmounted(() => {
 			document.removeEventListener("keyup", handleKeyUp);
@@ -1273,14 +1330,49 @@ export default defineComponent({
 					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);
+					// data.music = abc[0];
+					// handleResetRender(false);
 				};
 				reader.readAsText(file);
 			};
 			input.click();
 		};
 
+		/** 设置选段小节 */
+		const handleSetSelectMeares = (index: number | null, type: "start" | "end") => {
+			console.log("🚀 ~ index:", index);
+			if (type === "start") {
+				const note = index ? useIndexGetNote(`${index - 1}.0`) : null;
+				// console.log("🚀 ~ note:", note);
+				data.selectMeasures.start = index ? index - 1 : 0;
+				data.selectMeasures.startNote = note;
+				if (
+					data.selectMeasures.start &&
+					data.selectMeasures.end &&
+					data.selectMeasures.end < data.selectMeasures.start
+				) {
+					data.selectMeasures.end = 0;
+					data.selectMeasures.endNote = null;
+				}
+			} else {
+				const note = index
+					? useIndexGetNote(`${index - 1}.${abcData.abc.measures[index - 1]?.notes.length - 1}`)
+					: null;
+				// console.log("🚀 ~ note:", note);
+				data.selectMeasures.end = index ? index - 1 : 0;
+				data.selectMeasures.endNote = note;
+				if (
+					data.selectMeasures.start &&
+					data.selectMeasures.end &&
+					data.selectMeasures.start > data.selectMeasures.end
+				) {
+					// console.log(data.selectMeasures.start, data.selectMeasures.end);
+					data.selectMeasures.start = 0;
+					data.selectMeasures.startNote = null;
+				}
+			}
+		};
+
 		return () => (
 			<div class={styles.container}>
 				<div class={styles.containerTop} onKeyup={(e: Event) => e.stopPropagation()}>
@@ -1298,6 +1390,19 @@ export default defineComponent({
 									} else if (["png", "midi", "wav"].includes(val)) {
 										handleDownFile(val);
 									} else if (val === "print") {
+									} else if (val === "exit") {
+										// 判断是否在应用中
+										if (window.matchMedia("(display-mode: standalone)").matches) {
+											window.onbeforeunload = null;
+											window.parent.postMessage(
+												{
+													api: "notation_exit",
+												},
+												"*"
+											);
+										} else {
+											window.close();
+										}
 									}
 								}}
 							/>
@@ -1328,7 +1433,7 @@ export default defineComponent({
 								>
 									<img class={styles.topBtnIcon} src={item.icon} />
 								</div>
-								<div>{item.name}</div>
+								<div class={styles.btnName}>{item.name}</div>
 							</div>
 						))}
 
@@ -1474,9 +1579,10 @@ export default defineComponent({
 											filterable
 											options={instruments.value}
 											v-model:value={abcData.synthOptions.program}
-											onUpdate:value={() => {
+											onUpdate:value={async () => {
 												abcData.synthControl.disable(true);
 												data.playState = false;
+												await loadMiniMp3();
 												resetMidi(true);
 												popup.selectSubjectShow = false;
 											}}
@@ -1826,12 +1932,17 @@ export default defineComponent({
 							</NSpin>
 							<div>{data.playState ? "暂停" : "播放"}</div>
 						</div>
-						<div class={[styles.topBtn, styles.btnDisabled]}>
-							<div class={styles.btnImg}>
+						<div
+							id="selectMearesBtn"
+							class={[styles.topBtn]}
+							onClick={() => (popup.selectMearesShow = !popup.selectMearesShow)}
+						>
+							<div class={[styles.btnImg, popup.selectMearesShow && styles.btnImgActive]}>
 								<img class={styles.topBtnIcon} src={getImage("icon_22.png")} />
 							</div>
 							<div>选段</div>
 						</div>
+
 						<div class={[styles.topBtn, styles.btnDisabled]}>
 							<div class={styles.btnImg}>
 								<img class={styles.topBtnIcon} src={getImage("icon_23.png")} />
@@ -2044,18 +2155,7 @@ export default defineComponent({
 						<div id="paper"></div>
 						<Keys show={data.active ? true : false} onClick={(val) => handleChange(val)} />
 
-						{/* <div>
-							<textarea
-								ref={textAreaRef}
-								class={styles.value}
-								id="abc"
-								onChange={() => {
-									console.log(textAreaRef.value.value);
-									data.music = textAreaRef.value.value;
-									handleResetRender();
-								}}
-							></textarea>
-						</div> */}
+						{/* <textarea ref={textAreaRef} class={styles.value} id="abc"></textarea> */}
 						<div id="audio" style={{ opacity: 0 }}></div>
 						<div id="warnings"></div>
 
@@ -2068,6 +2168,62 @@ export default defineComponent({
 				<div ref={downRef}></div>
 
 				<TheSetting v-model:show={popup.settingShow} />
+
+				{data.selectMeasures.state && (
+					<UseDraggable
+						initialValue={{ x: data.selectMeasures.x, y: data.selectMeasures.y }}
+						class={[styles.selectMearesBox, !popup.selectMearesShow && styles.selectMearesHidden]}
+					>
+						<div onKeyup={(e: Event) => e.stopPropagation()}>
+							<NSpace justify="space-between">
+								<div class={styles.btnLineTitle}>输入小节范围</div>
+								<NButton circle quaternary size="small" onClick={() => (popup.selectMearesShow = false)}>
+									<NIcon size={16} component={<Close />} />
+								</NButton>
+							</NSpace>
+							<NSpace align="center" wrap={false} wrapItem={false}>
+								<div class={styles.mearesInput}>
+									<NInputNumber
+										min={1}
+										max={data.selectMeasures.max}
+										bordered={false}
+										placeholder="开始小节"
+										showButton={false}
+										onUpdate:value={(val) => handleSetSelectMeares(val, "start")}
+									></NInputNumber>
+									-
+									<NInputNumber
+										min={data.selectMeasures.start}
+										max={data.selectMeasures.max}
+										bordered={false}
+										placeholder="结束小节"
+										showButton={false}
+										onUpdate:value={(val) => handleSetSelectMeares(val, "end")}
+									></NInputNumber>
+								</div>
+								<div class={styles.topBtn}>
+									<NSpin show={data.loadingAudioSrouce} size="small">
+										<div
+											class={styles.btnImg}
+											onClick={() => togglePlay(data.playState ? "pause" : "play")}
+										>
+											<img
+												style={{ display: data.playState ? "" : "none" }}
+												class={styles.topBtnIcon}
+												src={getImage("icon_21_1.png")}
+											/>
+											<img
+												style={{ display: data.playState ? "none" : "" }}
+												class={styles.topBtnIcon}
+												src={getImage("icon_21.png")}
+											/>
+										</div>
+									</NSpin>
+								</div>
+							</NSpace>
+						</div>
+					</UseDraggable>
+				)}
 			</div>
 		);
 	},

+ 41 - 1
src/pc/home/noteData.ts

@@ -1,5 +1,35 @@
+export const ALL_NOTES = () => {
+	const keys = ["C", "^C", "D", "^D", "E", "F", "^F", "G", "^G", "A", "^A", "B"];
+	const notes = [];
+	const productKey = (total = 0) => {
+		if (total === 0) return "";
+		return new Array(Math.abs(total)).fill(total > 0 ? "'" : ",").join("");
+	};
+	for (let i = 0; i <= 6; i++) {
+		for (let j = 0; j < 12; j++) {
+			let note = keys[j];
+			if (i < 3) {
+				notes.push(note + productKey(i - 3));
+			} else if (i === 3) {
+				notes.push(note);
+			} else if (i === 4) {
+				note = note.toLocaleLowerCase();
+				notes.push(note);
+			} else {
+				note = note.toLocaleLowerCase();
+				notes.push(note + productKey(i - 4));
+			}
+		}
+	}
+	let str = "";
+	notes.forEach((note, index) => {
+		str += note + (index % 4 === 0 ? " |" : " ");
+	});
+	// console.log(notes);
+	return str;
+};
 export const ABC_NOTE_DATA = [
-	["^B,,,,,", "C,,,,"],
+	"C,,,,",
 	["^C,,,,", "_D,,,,"],
 	"D,,,,",
 	["^D,,,,", "_E,,,,"],
@@ -356,3 +386,13 @@ export const ABC_KEYS: { [_: string]: any } = {
 		"K:B": { up: 0, down: 0, move: 0 },
 	},
 };
+
+/** 附点音符 */
+export const NOTE_DOT: { [_: string]: string } = {
+	"4": "3",
+	"2": "3",
+	"": "3/2",
+	"/": "2/3",
+	"//": "1/3",
+	"///": "3/8",
+};

+ 19 - 14
src/pc/home/runtime.ts

@@ -213,8 +213,16 @@ export const renderMeasures = (abc: IAbc) => {
 			text += note.dynamics; // 力度符号
 			text += note.accidental; // 临时升降记号
 			text += note.content; // 音符
-			text += note.noteType; // 音符类型
-			text += note.dot; // 附点
+			// 音符时值
+			text += note.noteType;
+			text += note.dot;
+			
+			// if(note.dot){
+			// 	text += note.dot; // 附点
+			// } else {
+			// 	text += note.noteType; // 音符类型
+			// }
+
 			text += note.tieline; // 延音
 			if (note.tie.includes(")")) {
 				// 连音线 后
@@ -222,11 +230,8 @@ export const renderMeasures = (abc: IAbc) => {
 			}
 			text += note.segno; // 分割
 		}
-		text += measure.barline;
-		// text += `"${i}"` + measure.barline;
-		if (i > 0 && i % 4 === 0) {
-			text += "\n";
-		}
+		let _i = i + 1;
+		text += `"<${_i}"${ measure.barline}`;
 	}
 	// console.log(text)
 	return text;
@@ -286,14 +291,14 @@ export const formateAbc = (visualObj: TuneObject) => {
 		if (line.staff) {
 			for (let j = 0; j < line.staff.length; j++) {
 				const staff = line.staff[j];
-				if (i === 0){
-					if (staff.clef){
+				if (i === 0) {
+					if (staff.clef) {
 						abc.celf = `K:${staff.clef.type}`;
 					}
-					if (staff.key){
+					if (staff.key) {
 						abc.key = `K:${staff.key.root}${staff.key.acc}`;
 					}
-					if (staff.meter?.value?.[0]){
+					if (staff.meter?.value?.[0]) {
 						abc.meter = `M:${staff.meter.value[0].num}/${staff.meter.value[0].den}`;
 					}
 				}
@@ -302,8 +307,8 @@ export const formateAbc = (visualObj: TuneObject) => {
 						const voice = staff.voices[k];
 						for (let l = 0; l < voice.length; l++) {
 							const element = voice[l];
-							if (element.el_type === "bar"){
-								measureIndex++
+							if (element.el_type === "bar") {
+								measureIndex++;
 							}
 							if (element.el_type === "note") {
 								list.push(element);
@@ -314,5 +319,5 @@ export const formateAbc = (visualObj: TuneObject) => {
 			}
 		}
 	}
-	console.log(measureIndex)
+	console.log(measureIndex);
 };