|
@@ -40,6 +40,7 @@ import {
|
|
NSelect,
|
|
NSelect,
|
|
NSpace,
|
|
NSpace,
|
|
NSpin,
|
|
NSpin,
|
|
|
|
+ useDialog,
|
|
useMessage,
|
|
useMessage,
|
|
} from "naive-ui";
|
|
} from "naive-ui";
|
|
import { LongArrowAltDown, LongArrowAltUp, GripLinesVertical } from "@vicons/fa";
|
|
import { LongArrowAltDown, LongArrowAltUp, GripLinesVertical } from "@vicons/fa";
|
|
@@ -50,85 +51,13 @@ import TheSetting from "./component/the-setting";
|
|
import { useRoute } from "vue-router";
|
|
import { useRoute } from "vue-router";
|
|
import { api_musicSheetCreationDetail, api_musicSheetCreationUpdate } from "../api";
|
|
import { api_musicSheetCreationDetail, api_musicSheetCreationUpdate } from "../api";
|
|
import instrumentsNames from "/src/constant/instrmentsNames.json";
|
|
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 { Close } from "@vicons/ionicons5";
|
|
import { UseDraggable } from "@vueuse/components";
|
|
import { UseDraggable } from "@vueuse/components";
|
|
import { getQuery } from "/src/utils/queryString";
|
|
import { getQuery } from "/src/utils/queryString";
|
|
import Metronome, { metronomeData } from "/src/helpers/metronome";
|
|
import Metronome, { metronomeData } from "/src/helpers/metronome";
|
|
import cleanDeep from "clean-deep";
|
|
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[] => {
|
|
export const initMusic = (total: number): IMeasure[] => {
|
|
return new Array(total).fill(0).map((item, index) => {
|
|
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) {
|
|
function moveNote(note: string, step: number) {
|
|
- var x = allPitches.indexOf(note);
|
|
|
|
|
|
+ var x = ALL_Pitches.indexOf(note);
|
|
if (x >= 0) {
|
|
if (x >= 0) {
|
|
- const _note = allPitches[x - step];
|
|
|
|
|
|
+ const _note = ALL_Pitches[x - step];
|
|
return _note ? _note : note;
|
|
return _note ? _note : note;
|
|
}
|
|
}
|
|
return note;
|
|
return note;
|
|
@@ -175,6 +104,7 @@ function moveNote(note: string, step: number) {
|
|
export default defineComponent({
|
|
export default defineComponent({
|
|
name: "Home",
|
|
name: "Home",
|
|
setup() {
|
|
setup() {
|
|
|
|
+ const dialog = useDialog();
|
|
const route = useRoute();
|
|
const route = useRoute();
|
|
const message = useMessage();
|
|
const message = useMessage();
|
|
const popup = reactive({
|
|
const popup = reactive({
|
|
@@ -197,7 +127,7 @@ export default defineComponent({
|
|
const data = reactive({
|
|
const data = reactive({
|
|
loading: true,
|
|
loading: true,
|
|
drawCount: 0,
|
|
drawCount: 0,
|
|
- isSave: false,
|
|
|
|
|
|
+ isSave: true,
|
|
musicId: "",
|
|
musicId: "",
|
|
musicName: "", // 曲谱名称
|
|
musicName: "", // 曲谱名称
|
|
creator: "", // 创建者
|
|
creator: "", // 创建者
|
|
@@ -236,6 +166,8 @@ export default defineComponent({
|
|
loadingAudioSrouce: false, // 加载音频资源
|
|
loadingAudioSrouce: false, // 加载音频资源
|
|
/** 移调类型 */
|
|
/** 移调类型 */
|
|
moveKeyType: "inset" as "inset" | "up" | "down", // 移调类型
|
|
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 noteTypes = ABC_DATA.types.map((item) => item.value).filter(Boolean);
|
|
const accidentals = ABC_DATA.accidentals.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("开始");
|
|
console.log("开始");
|
|
data.playState = true;
|
|
data.playState = true;
|
|
var svg = document.querySelector("#paper svg");
|
|
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, "x1", "0");
|
|
cursor.setAttributeNS(null, "y1", "0");
|
|
cursor.setAttributeNS(null, "y1", "0");
|
|
cursor.setAttributeNS(null, "x2", "0");
|
|
cursor.setAttributeNS(null, "x2", "0");
|
|
cursor.setAttributeNS(null, "y2", "0");
|
|
cursor.setAttributeNS(null, "y2", "0");
|
|
- svg?.appendChild(cursor);
|
|
|
|
},
|
|
},
|
|
onBeat: function (beatNumber: any, totalBeats: any, totalTime: any) {
|
|
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 (!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 (ev.measureStart && ev.left === null) return; // this was the second part of a tie across a measure line. Just ignore it.
|
|
|
|
+
|
|
if (popup.selectMearesShow) {
|
|
if (popup.selectMearesShow) {
|
|
const startTime = data.selectMeasures.startNote?.currentTrackMilliseconds || 0;
|
|
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;
|
|
const endNote: any = data.selectMeasures.endNote ? data.selectMeasures.endNote : null;
|
|
- // console.log("🚀 ~ endNote:", ev.milliseconds , endNote.currentTrackMilliseconds)
|
|
|
|
if (
|
|
if (
|
|
ev.milliseconds < startTime ||
|
|
ev.milliseconds < startTime ||
|
|
(endNote && ev.milliseconds > endNote.currentTrackMilliseconds)
|
|
(endNote && ev.milliseconds > endNote.currentTrackMilliseconds)
|
|
@@ -395,12 +336,12 @@ export default defineComponent({
|
|
const totalTime = (abcData.synthControl as any).visualObj.getTotalTime();
|
|
const totalTime = (abcData.synthControl as any).visualObj.getTotalTime();
|
|
if (totalTime) {
|
|
if (totalTime) {
|
|
const progress = startTime / 1000 / 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");
|
|
var cursor = document.querySelector("#paper svg .ABCJS-cursor");
|
|
if (cursor) {
|
|
if (cursor) {
|
|
cursor.setAttribute("x1", ev.left + ev.width / 2);
|
|
cursor.setAttribute("x1", ev.left + ev.width / 2);
|
|
@@ -462,10 +403,23 @@ export default defineComponent({
|
|
data.playState = true;
|
|
data.playState = true;
|
|
} else if (type === "pause") {
|
|
} else if (type === "pause") {
|
|
abcData.synthControl.play();
|
|
abcData.synthControl.play();
|
|
|
|
+ // abcData.synthControl.pause();
|
|
data.playState = false;
|
|
data.playState = false;
|
|
hideCursor();
|
|
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 {
|
|
} else {
|
|
abcData.synthControl.restart();
|
|
abcData.synthControl.restart();
|
|
|
|
+ nextTick(() => {
|
|
|
|
+ if (!data.playState) {
|
|
|
|
+ abcData.synthControl.play();
|
|
|
|
+ }
|
|
|
|
+ });
|
|
}
|
|
}
|
|
// console.log("🚀 ~ abcData.synthControl:", abcData.synthControl.timer.noteTimings);
|
|
// console.log("🚀 ~ abcData.synthControl:", abcData.synthControl.timer.noteTimings);
|
|
};
|
|
};
|
|
@@ -519,6 +473,14 @@ export default defineComponent({
|
|
data.selectMeasures.max = measureNumber;
|
|
data.selectMeasures.max = measureNumber;
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+ let saveTimer: any = null;
|
|
|
|
+ const autoSave = () => {
|
|
|
|
+ saveTimer && clearTimeout(saveTimer);
|
|
|
|
+ saveTimer = setTimeout(() => {
|
|
|
|
+ handleSaveMusic(false);
|
|
|
|
+ }, 5000);
|
|
|
|
+ };
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* @param isProduct 是否是生成曲谱
|
|
* @param isProduct 是否是生成曲谱
|
|
*/
|
|
*/
|
|
@@ -528,16 +490,37 @@ export default defineComponent({
|
|
if (data.playState) {
|
|
if (data.playState) {
|
|
data.playState = false;
|
|
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) => {
|
|
return new Promise((resolve) => {
|
|
nextTick(() => {
|
|
nextTick(() => {
|
|
data.music = renderMeasures(abcData.abc);
|
|
data.music = renderMeasures(abcData.abc);
|
|
renderSvg();
|
|
renderSvg();
|
|
resetMidi(data.drawCount > 0 ? true : false);
|
|
resetMidi(data.drawCount > 0 ? true : false);
|
|
renderBoxRect();
|
|
renderBoxRect();
|
|
- // productMetronomeData();
|
|
|
|
|
|
+ try {
|
|
|
|
+ productMetronomeData();
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.log("🚀 ~ error:", error);
|
|
|
|
+ }
|
|
|
|
+
|
|
resolve(1);
|
|
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);
|
|
textAreaRef.value && (textAreaRef.value.value = data.music);
|
|
data.drawCount++;
|
|
data.drawCount++;
|
|
@@ -548,7 +531,9 @@ export default defineComponent({
|
|
/** 生成曲谱节拍器数据 */
|
|
/** 生成曲谱节拍器数据 */
|
|
const productMetronomeData = () => {
|
|
const productMetronomeData = () => {
|
|
const times = new ABCJS.TimingCallbacks(abcData.visualObj);
|
|
const times = new ABCJS.TimingCallbacks(abcData.visualObj);
|
|
|
|
+ data.times = times.noteTimings;
|
|
const list: any[] = [];
|
|
const list: any[] = [];
|
|
|
|
+ let meter = abcData.abc.meter || "";
|
|
// length - 1是为了去除最后一个空的结束事件
|
|
// length - 1是为了去除最后一个空的结束事件
|
|
for (let i = 0; i < times.noteTimings.length - 1; i++) {
|
|
for (let i = 0; i < times.noteTimings.length - 1; i++) {
|
|
const timeNote = times.noteTimings[i];
|
|
const timeNote = times.noteTimings[i];
|
|
@@ -557,7 +542,10 @@ export default defineComponent({
|
|
indexStr = indexStr.split(".").map((n: string) => Number(n));
|
|
indexStr = indexStr.split(".").map((n: string) => Number(n));
|
|
if (indexStr.length === 2) {
|
|
if (indexStr.length === 2) {
|
|
const measure = abcData.abc.measures[indexStr[0]];
|
|
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 reg = new RegExp(/M:(\d+)\/\d+/);
|
|
const numerator = Number(meter.match(reg)?.[1]);
|
|
const numerator = Number(meter.match(reg)?.[1]);
|
|
// console.log("🚀 ~ reg:", meter.match(reg)?.[1], abcData.abc.meter)
|
|
// 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 {
|
|
try {
|
|
|
|
+ metronomeData.activeIndex = -1;
|
|
metronomeData.metro.init(list);
|
|
metronomeData.metro.init(list);
|
|
} catch (error) {
|
|
} catch (error) {
|
|
console.log("🚀 ~ 生成节拍器数据错误:", error);
|
|
console.log("🚀 ~ 生成节拍器数据错误:", error);
|
|
@@ -609,6 +600,32 @@ export default defineComponent({
|
|
return notes;
|
|
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
|
|
* @param key
|
|
@@ -626,26 +643,42 @@ export default defineComponent({
|
|
abcData.abc.measures[data.active?.measureIndex]?.notes[data.active?.noteIndex] || null;
|
|
abcData.abc.measures[data.active?.measureIndex]?.notes[data.active?.noteIndex] || null;
|
|
|
|
|
|
if (type === "exit") {
|
|
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);
|
|
// console.log(params, activeNote);
|
|
if (type === "type") {
|
|
if (type === "type") {
|
|
@@ -817,7 +850,7 @@ export default defineComponent({
|
|
|
|
|
|
// 拍号
|
|
// 拍号
|
|
if (type === "meter") {
|
|
if (type === "meter") {
|
|
- if (data.active) {
|
|
|
|
|
|
+ if (data.active && data.active.measureIndex !== 0) {
|
|
if (!activeNote) return;
|
|
if (!activeNote) return;
|
|
const measure = abcData.abc.measures[data.active.measureIndex];
|
|
const measure = abcData.abc.measures[data.active.measureIndex];
|
|
measure.meter = `[${value}]`;
|
|
measure.meter = `[${value}]`;
|
|
@@ -1008,7 +1041,7 @@ export default defineComponent({
|
|
|
|
|
|
// 移动音符
|
|
// 移动音符
|
|
if (type === "move") {
|
|
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;
|
|
if (!activeNote) return;
|
|
activeNote.content = moveNote(activeNote.content, step);
|
|
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("").
|
|
// 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) {
|
|
if (abcData.abc.measures[data.active.measureIndex].notes.length === 0) {
|
|
abcData.abc.measures.splice(data.active.measureIndex, 1);
|
|
abcData.abc.measures.splice(data.active.measureIndex, 1);
|
|
}
|
|
}
|
|
- handleResetRender();
|
|
|
|
|
|
+ await handleResetRender();
|
|
data.active = null as unknown as INoteActive;
|
|
data.active = null as unknown as INoteActive;
|
|
}
|
|
}
|
|
};
|
|
};
|
|
@@ -1179,6 +1212,10 @@ export default defineComponent({
|
|
value: index,
|
|
value: index,
|
|
}));
|
|
}));
|
|
});
|
|
});
|
|
|
|
+ const instrumentName = computed(() => {
|
|
|
|
+ const code = ABCJS.synth.instrumentIndexToName[abcData.synthOptions.program];
|
|
|
|
+ return instrumentsNames[code as keyof typeof instrumentsNames];
|
|
|
|
+ });
|
|
const getDetailData = async () => {
|
|
const getDetailData = async () => {
|
|
data.loading = true;
|
|
data.loading = true;
|
|
const query: any = getQuery();
|
|
const query: any = getQuery();
|
|
@@ -1197,8 +1234,8 @@ export default defineComponent({
|
|
console.log("🚀 ~ abc:", abc);
|
|
console.log("🚀 ~ abc:", abc);
|
|
|
|
|
|
abcData.abc.celf = abc.celf || "K:treble";
|
|
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.speed = abc.speed || "Q:1/4=60";
|
|
abcData.abc.visualTranspose = abc.visualTranspose || 0;
|
|
abcData.abc.visualTranspose = abc.visualTranspose || 0;
|
|
abcData.abc.subjectCode = abc.subjectCode || "acoustic_grand_piano";
|
|
abcData.abc.subjectCode = abc.subjectCode || "acoustic_grand_piano";
|
|
@@ -1209,6 +1246,7 @@ export default defineComponent({
|
|
}
|
|
}
|
|
}
|
|
}
|
|
data.loading = false;
|
|
data.loading = false;
|
|
|
|
+ return res;
|
|
};
|
|
};
|
|
const handleSaveMusic = async (tips = true) => {
|
|
const handleSaveMusic = async (tips = true) => {
|
|
await api_musicSheetCreationUpdate({
|
|
await api_musicSheetCreationUpdate({
|
|
@@ -1226,7 +1264,6 @@ export default defineComponent({
|
|
};
|
|
};
|
|
onMounted(async () => {
|
|
onMounted(async () => {
|
|
await getDetailData();
|
|
await getDetailData();
|
|
- loadMiniMp3();
|
|
|
|
if (ABCJS.synth.supportsAudio()) {
|
|
if (ABCJS.synth.supportsAudio()) {
|
|
abcData.synthControl = new ABCJS.synth.SynthController();
|
|
abcData.synthControl = new ABCJS.synth.SynthController();
|
|
// console.log("🚀 ~ abcData.synthControl:", abcData.synthControl)
|
|
// console.log("🚀 ~ abcData.synthControl:", abcData.synthControl)
|
|
@@ -1240,6 +1277,7 @@ export default defineComponent({
|
|
|
|
|
|
console.log(ABCJS);
|
|
console.log(ABCJS);
|
|
await handleResetRender();
|
|
await handleResetRender();
|
|
|
|
+ loadMiniMp3();
|
|
document.addEventListener("keyup", handleKeyUp);
|
|
document.addEventListener("keyup", handleKeyUp);
|
|
window.onbeforeunload = (e) => {
|
|
window.onbeforeunload = (e) => {
|
|
if (!data.isSave) {
|
|
if (!data.isSave) {
|
|
@@ -1248,7 +1286,6 @@ export default defineComponent({
|
|
}
|
|
}
|
|
};
|
|
};
|
|
abcData.synthControl.restart();
|
|
abcData.synthControl.restart();
|
|
- // formateAbc(abcData.visualObj);
|
|
|
|
const selectMearesBtn = document.querySelector("#selectMearesBtn");
|
|
const selectMearesBtn = document.querySelector("#selectMearesBtn");
|
|
if (selectMearesBtn) {
|
|
if (selectMearesBtn) {
|
|
const rect = selectMearesBtn.getBoundingClientRect();
|
|
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) => {
|
|
const handleDownFile = (type: IFileBtnType) => {
|
|
if (type === "png") {
|
|
if (type === "png") {
|
|
downPng();
|
|
downPng();
|
|
@@ -1404,6 +1455,8 @@ export default defineComponent({
|
|
downMidi();
|
|
downMidi();
|
|
} else if (type === "wav") {
|
|
} else if (type === "wav") {
|
|
downWav();
|
|
downWav();
|
|
|
|
+ } else if (type === "down-xml") {
|
|
|
|
+ downXML();
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
@@ -1413,16 +1466,19 @@ export default defineComponent({
|
|
input.accept = ".xml,.musicxml";
|
|
input.accept = ".xml,.musicxml";
|
|
input.onchange = (e: any) => {
|
|
input.onchange = (e: any) => {
|
|
const file = e.target.files[0];
|
|
const file = e.target.files[0];
|
|
|
|
+
|
|
const reader = new FileReader();
|
|
const reader = new FileReader();
|
|
reader.onload = (e: any) => {
|
|
reader.onload = (e: any) => {
|
|
let abc = e.target.result;
|
|
let abc = e.target.result;
|
|
- console.log("🚀 ~ abc:", abc);
|
|
|
|
|
|
+ // console.log("🚀 ~ abc:", abc);
|
|
abc = new DOMParser().parseFromString(abc, "text/xml");
|
|
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 });
|
|
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);
|
|
reader.readAsText(file);
|
|
};
|
|
};
|
|
@@ -1485,7 +1541,7 @@ export default defineComponent({
|
|
} else if (["xml"].includes(val)) {
|
|
} else if (["xml"].includes(val)) {
|
|
handleExport();
|
|
handleExport();
|
|
} else if (val === "upload") {
|
|
} else if (val === "upload") {
|
|
- } else if (["png", "midi", "wav"].includes(val)) {
|
|
|
|
|
|
+ } else if (["png", "midi", "wav", "down-xml"].includes(val)) {
|
|
handleDownFile(val);
|
|
handleDownFile(val);
|
|
} else if (val === "print") {
|
|
} else if (val === "print") {
|
|
}
|
|
}
|
|
@@ -1656,7 +1712,16 @@ export default defineComponent({
|
|
<div class={styles.btnImg} onClick={() => (popup.instrument = true)}>
|
|
<div class={styles.btnImg} onClick={() => (popup.instrument = true)}>
|
|
<img class={styles.topBtnIcon} src={getImage("icon_25.png")} />
|
|
<img class={styles.topBtnIcon} src={getImage("icon_25.png")} />
|
|
</div>
|
|
</div>
|
|
- <div>选择声部</div>
|
|
|
|
|
|
+ <div
|
|
|
|
+ style={{
|
|
|
|
+ overflow: "hidden",
|
|
|
|
+ textOverflow: "ellipsis",
|
|
|
|
+ whiteSpace: "nowrap",
|
|
|
|
+ maxWidth: "60px",
|
|
|
|
+ }}
|
|
|
|
+ >
|
|
|
|
+ {instrumentName.value}
|
|
|
|
+ </div>
|
|
</div>
|
|
</div>
|
|
),
|
|
),
|
|
default: () => (
|
|
default: () => (
|
|
@@ -2008,14 +2073,13 @@ export default defineComponent({
|
|
|
|
|
|
<div class={styles.topLine}></div>
|
|
<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>
|
|
</div>
|
|
</div>
|
|
<div class={styles.topBtn}>
|
|
<div class={styles.topBtn}>
|
|
@@ -2049,8 +2113,14 @@ export default defineComponent({
|
|
<div>选段</div>
|
|
<div>选段</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")} />
|
|
<img class={styles.topBtnIcon} src={getImage("icon_23.png")} />
|
|
</div>
|
|
</div>
|
|
<div>节拍器</div>
|
|
<div>节拍器</div>
|
|
@@ -2284,13 +2354,8 @@ export default defineComponent({
|
|
)}
|
|
)}
|
|
|
|
|
|
{/* <textarea ref={textAreaRef} class={styles.value} id="abc"></textarea> */}
|
|
{/* <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="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 && (
|
|
{data.loadingAudioSrouce && (
|
|
<div class={styles.loading}>
|
|
<div class={styles.loading}>
|
|
<NSpin></NSpin>
|
|
<NSpin></NSpin>
|
|
@@ -2323,7 +2388,7 @@ export default defineComponent({
|
|
<div class={styles.mearesInput}>
|
|
<div class={styles.mearesInput}>
|
|
<NInputNumber
|
|
<NInputNumber
|
|
min={1}
|
|
min={1}
|
|
- max={data.selectMeasures.max}
|
|
|
|
|
|
+ max={data.selectMeasures.end || data.selectMeasures.max}
|
|
bordered={false}
|
|
bordered={false}
|
|
placeholder="开始小节"
|
|
placeholder="开始小节"
|
|
showButton={false}
|
|
showButton={false}
|