import { computed, defineComponent, nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue"; import ABCJS, { AbcElem, AbcVisualParams, ClickListenerAnalysis, ClickListenerDrag, NoteTimingEvent, SynthObjectController, } from "abcjs"; import "ABCJS/ABCJS-audio.css"; import styles from "./index.module.less"; import { showConfirmDialog, showToast } from "vant"; import Keys from "../component/keys"; import { Collapse, CollapseItem } from "@varlet/ui"; import { IAbc, IMeasure, INote, INoteActive } from "../types"; import { ABC_DATA, createMeasure, createNote, formateAbc, getKeyStep, moveNoteKey, renderMeasures, } from "./runtime"; import TheIcon from "/src/components/The-icon"; import { cloneDeep } from "lodash"; import TheSpeed from "./component/the-speed"; import { getImage } from "./images"; import { NButton, NDropdown, NGi, NGrid, NIcon, NInput, NInputNumber, NModal, NPopover, NPopselect, NSelect, NSpace, NSpin, useDialog, useMessage, } from "naive-ui"; import { LongArrowAltDown, LongArrowAltUp, GripLinesVertical } from "@vicons/fa"; import { svg2canvas } from "/src/utils/svg2canvas"; import { decodeUrl, downloadFile } from "/src/utils"; import FileBtn, { IFileBtnType } from "./component/file-btn"; import TheSetting from "./component/the-setting"; import { useRoute } from "vue-router"; import { api_musicSheetCreationDetail, api_musicSheetCreationSave, api_musicSheetCreationUpdate, api_xmlToAbc, } from "../api"; import instrumentsNames from "/src/constant/instrmentsNames.json"; import { ALL_NOTES, ALL_Pitches } from "./noteData"; import { Close } from "@vicons/ionicons5"; import { getQuery } from "/src/utils/queryString"; import Metronome, { metronomeData } from "/src/helpers/metronome"; import cleanDeep from "clean-deep"; import { saveAs } from "file-saver"; import qs from "query-string"; import { useDocumentVisibility } from "@vueuse/core"; import request from "/src/utils/request"; import { api_uploadFile } from "/src/utils/uploadFile"; import { bufferToWave } from "/src/helpers/parseABC"; import UploadToResources from "../component/upload-to-resources"; export const initMusic = (total: number): IMeasure[] => { return new Array(total).fill(0).map((item, index) => { return { measureNumber: index + 1, barline: "|", celf: "", key: "", repeat: "", meter: "", notes: [ { accidental: "", clef: "", meter: "", content: "z", noteType: "4", play: [], key: "", speed: "", dynamics: "", dCode: "", tie: "", tCode: "", dot: "", slus: "", tieline: "", segno: "", }, ], }; }); }; function moveNote(note: string, step: number) { var x = ALL_Pitches.indexOf(note); if (x >= 0) { const _note = ALL_Pitches[x - step]; return _note ? _note : note; } return note; } export default defineComponent({ name: "Home", setup() { const dialog = useDialog(); const route = useRoute(); const message = useMessage(); const popup = reactive({ instrument: false, selectSubjectShow: false, // 声部弹窗 moveKeyShow: false, // 移调弹窗 speedShow: false, // 节拍弹窗 accidentalsShow: false, // 临时升降记号弹窗 clefShow: false, // 谱号弹窗 keyShow: false, // 调号弹窗 meterShow: false, // 拍号弹窗 playShow: false, // 演奏技法弹窗 dynamicsShow: false, // 力度标记弹窗 barShow: false, // 小节弹窗 mearseDeleteShow: false, staffShow: false, // 五线谱弹窗 settingShow: false, // 设置弹窗 selectMearesShow: false, // 选择小节弹窗 }); const data = reactive({ uploadStatus: "", saveLoading: false, saveLoadingText: false, loading: true, drawCount: 0, isSave: true, musicId: Date.now().toString(), musicName: "", // 曲谱名称 creator: "", // 创建者 subjectId: "", // 声部 speed: "", music: "", playState: false, // 播放状态 active: null as unknown as INoteActive, select: { state: false, list: [] as any[], parmas: null as any, }, isClickNote: false, /** 音符类型 */ noteType: "", /** 选择小节范围 */ 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"], morePlay: false, // 更多演奏技法 addMearseType: "pre" as "pre" | "next" | "finish", // 添加小节类型 addMearseNumber: 1, // 添加小节数量 deleteMearseType: "ing" as "ing" | "finish", // 删除小节类型 loadingAudioSrouce: false, // 加载音频资源 /** 移调类型 */ moveKeyType: "inset" as "inset" | "up" | "down", // 移调类型 activePlayNote: null as any, // 当前演奏音符 times: [] as any[], // 节拍器数据 undoList: [] as any[], // 撤销列表 redoList: [] as any[], // 重做列表 uploadShow: false, // 上传弹窗 item: {} as any, // 上传数据 uploadClick: false, // 上传点击 /** 多选 */ multiSelect: false, multiSelectList: [] as any[], multilList: [] as any[], }); const noteTypes = ABC_DATA.types.map((item) => item.value).filter(Boolean); const accidentals = ABC_DATA.accidentals.map((item) => item.value).filter(Boolean); const clefs = ABC_DATA.clef.map((item) => item.value).filter(Boolean); const playTypes = ABC_DATA.play.map((item) => item.value).filter(Boolean); const dynamics = ABC_DATA.dynamics .map((item) => item.value) .flat() .filter(Boolean); const barTypes = ABC_DATA.bar.map((item) => item.value).filter(Boolean); console.log("🚀 ~ noteTypes:", noteTypes, accidentals, clefs, playTypes, dynamics); const setNoteColor = () => { // const gs = document.querySelectorAll(`#paper svg g[fill="#0f81ff"]`); // Array.from(gs).forEach((g) => { // g.classList.remove("note_selected"); // g.setAttribute("fill", "currentColor"); // }); const gs1 = document.querySelectorAll(`#paper svg g.note_selected`); Array.from(gs1).forEach((g) => { g.classList.remove("note_selected"); }); }; /** 点击音符 */ const clickListener = ( abcElem: AbcElem, tuneNumber: number, classes: string, analysis: ClickListenerAnalysis, drag: ClickListenerDrag ) => { setNoteColor(); // console.log("🚀 ~ data.select.state:", abcData.visualObj); let indexStr: any = abcElem.chord?.find((n) => n.position === "left")?.name || ""; indexStr = indexStr.split(".").map((n: string) => Number(n)); const active = { ...cloneDeep(abcElem), measureIndex: indexStr[0], noteIndex: indexStr[1], isFirstChecked: true, }; if (abcElem) { if (abcElem.startChar && abcElem.endChar) { abcData.visualObj.engraver.rangeHighlight(abcElem.startChar, abcElem.endChar); abcElem?.abselem?.elemset?.[0]?.classList?.add("note_selected"); } } // 选择范围模式 if (data.select.state) { data.select.list.push(active); if (data.select.list.length === 1) { showToast("请先选择结束音符"); } if (data.select.list.length === 2) { console.log(data.select.list); data.select.list = data.select.list.sort((a, b) => a.startChar - b.startChar); handleSelectNote(); } return; } data.active = active; if (data.multiSelect) { if (data.multiSelectList.length === 0) { data.multiSelectList.push(active); } console.log("🚀 ~ data.multiSelectList:", data.multiSelectList); if (data.multiSelectList.length > 0) { data.multilList = []; data.multiSelectList = [data.multiSelectList[0], active]; data.multiSelectList = data.multiSelectList.sort((a, b) => a.startChar - b.startChar); data.multilList = getmutilList(); handleChange({ type: "multiSelect", value: "" }); return; } } else { if (data.multiSelectList.length || data.multilList.length) { handleClearMultiSelect(); } } console.log( "🚀 ~ abcElem:", abcElem, data.music.substring(data.active.startChar, data.active.endChar) ); if (data.active?.el_type === "note" && !popup.selectMearesShow) { const totalTime = (abcData.synthControl as any).visualObj.getTotalTime(); if (totalTime) { const progress = (data.active as any).currentTrackMilliseconds / 1000 / totalTime; // console.log("🚀 ~ data.active:", progress); (abcData.synthControl as any).seek(progress); } } // if (abcElem.el_type === "tempo") { // abcData.visualObj.engraver.rangeHighlight(abcElem.startChar, abcElem.endChar); // } if (drag && drag.step) { handleChange({ type: "move", value: { action: "drag", step: drag.step } }); return; } if (!abcElem?.midiPitches) return; ABCJS.synth.playEvent(abcElem.midiPitches, abcElem.midiGraceNotePitches, 1000); }; const getmutilList = () => { const start = data.multiSelectList[0]; const end = data.multiSelectList[1]; const list = [] as any[]; for (let i = start.measureIndex; i < end.measureIndex + 1; i++) { const measure = abcData.abc.measures[i]; for (let j = 0; j < measure.notes.length; j++) { const abcElem = useIndexGetNote(`${i}.${j}`); const active = { ...cloneDeep(abcElem), measureIndex: i, noteIndex: j, }; if (start.measureIndex === i) { j >= start.noteIndex && list.push(active); } else if (i === end.measureIndex) { j <= end.noteIndex && list.push(active); } else { list.push(active); } } } // console.log(list); return list; }; const textAreaRef = ref(); const abcData = reactive({ visualObj: null as any, midiBuffer: null as unknown as ABCJS.MidiBuffer, abcOptions: { selectionColor: "#0f81ff", jazzchords: true, add_classes: true, clickListener: clickListener, responsive: "resize", dragging: true, selectTypes: ["note"], visualTranspose: 0, wrap: { minSpacing: 0.1, maxSpacing: 2.7, preferredMeasuresPerLine: 4, }, staffwidth: 800, } as AbcVisualParams, synthControl: null as unknown as SynthObjectController, synthOptions: { program: 0, soundFontUrl: "https://oss.dayaedu.com/musicSheet/", // soundFontUrl: "https://paulrosen.github.io/midi-js-soundfonts/FluidR3_GM/", // 默认 FluidR3_GM // soundFontUrl: "https://paulrosen.github.io/midi-js-soundfonts/MusyngKite/", // Musyng Kite }, abc: { celf: "K:treble", minUnit: "L:1/4", meter: "M:4/4", speed: "Q:1/4=60", key: "K:C", visualTranspose: 0, visualKey: "K:C", subjectCode: "acoustic_grand_piano", measures: initMusic(30), } as IAbc, }); /** 添加音符 */ const handleCreateNote = (measureIndex: number, noteIndex: number, options: INote) => { // console.log("🚀 ~ noteIndex:", noteIndex); const measure = abcData.abc.measures[measureIndex]; if (measure) { measure.notes.splice(noteIndex + 1, 0, options); } }; const hideCursor = () => { const cursor = document.querySelector("#paper svg .ABCJS-cursor"); if (cursor) { cursor.setAttribute("x1", "0"); cursor.setAttribute("x2", "0"); cursor.setAttribute("y1", "0"); cursor.setAttribute("y2", "0"); } }; const cursorControl = { onReady: function () {}, onStart: function () { console.log("开始"); data.playState = true; var svg = document.querySelector("#paper svg"); 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"); }, onBeat: function (beatNumber: any, totalBeats: any, totalTime: any) { if (!data.playState) return; if (popup.selectMearesShow && data.selectMeasures.startNote && data.selectMeasures.endNote) { const end = totalBeats * (data.selectMeasures.endNote.milliseconds / (abcData.visualObj.getTotalTime() * 1000)) - 0.1; // console.log(beatNumber, start, end, progress); if (beatNumber >= end) { const progress = data.selectMeasures.startNote.milliseconds / (abcData.visualObj.getTotalTime() * 1000); (abcData.synthControl as any).seek(progress); return; } } // console.log("🚀 ~ beatNumber:", beatNumber, totalBeats, totalTime); const time = (beatNumber / totalBeats) * totalTime; metronomeData.metro.sound(time); }, 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. data.activePlayNote = { ...ev }; var cursor = document.querySelector("#paper svg .ABCJS-cursor"); if (cursor) { cursor.setAttribute("x1", ev.left + ev.width / 2); cursor.setAttribute("x2", ev.left + ev.width / 2); cursor.setAttribute("y1", ev.top); cursor.setAttribute("y2", ev.top + ev.height); } }, onFinished: function () { console.log("finished"); data.playState = false; var els = document.querySelectorAll("svg .highlight"); for (var i = 0; i < els.length; i++) { els[i].classList.remove("highlight"); } hideCursor(); }, }; const staffNotes = ALL_NOTES(); const loadMiniMp3 = async () => { data.loadingAudioSrouce = true; const midiBuffer = new ABCJS.synth.CreateSynth(); 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 }, }) .then(() => { abcData.synthControl .setTune(abcData.visualObj, useActive, { midiTranspose: abcData.abc.visualTranspose, program: abcData.synthOptions.program, }) .then(function (response) { data.loadingAudioSrouce = false; // console.log("Audio successfully loaded.", {...abcData.synthControl}); // console.log("🚀 ~ abcData.synthControl:", abcData.synthControl); }) .catch((err) => { console.log(err); }); }); }; const togglePlay = async (type: "play" | "pause" | "reset") => { if (type === "play") { if (popup.selectMearesShow) { if ( data.selectMeasures.start > data.selectMeasures.end || !data.selectMeasures.startNote || !data.selectMeasures.endNote ) { data.selectMeasures.start = 0; data.selectMeasures.end = 0; data.selectMeasures.startNote = null; data.selectMeasures.endNote = null; message.warning("请输入正确的小节范围"); data.selectMeasures.state = false; nextTick(() => { data.selectMeasures.state = true; }); return; } if (!(abcData.synthControl as any).isLoaded) { (abcData.synthControl as any).runWhenReady(() => { const progress = data.selectMeasures.startNote.milliseconds / (abcData.visualObj.getTotalTime() * 1000); (abcData.synthControl as any).seek(progress); }); } else { const progress = data.selectMeasures.startNote.milliseconds / (abcData.visualObj.getTotalTime() * 1000); (abcData.synthControl as any).seek(progress); } } abcData.synthControl.play(); 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 (popup.selectMearesShow) { if ( data.selectMeasures.start > data.selectMeasures.end || !data.selectMeasures.startNote || !data.selectMeasures.endNote ) { data.selectMeasures.start = 0; data.selectMeasures.end = 0; data.selectMeasures.startNote = null; data.selectMeasures.endNote = null; message.warning("请输入正确的小节范围"); data.selectMeasures.state = false; nextTick(() => { data.selectMeasures.state = true; }); return; } if (!(abcData.synthControl as any).isLoaded) { (abcData.synthControl as any).runWhenReady(() => { const progress = data.selectMeasures.startNote.milliseconds / (abcData.visualObj.getTotalTime() * 1000); (abcData.synthControl as any).seek(progress); }); } else { const progress = data.selectMeasures.startNote.milliseconds / (abcData.visualObj.getTotalTime() * 1000); (abcData.synthControl as any).seek(progress); } } if (!data.playState) { abcData.synthControl.play(); } }); } // console.log("🚀 ~ abcData.synthControl:", abcData.synthControl.timer.noteTimings); }; const renderSvg = () => { abcData.visualObj = ABCJS.renderAbc("paper", data.music, { ...abcData.abcOptions, visualTranspose: abcData.abc.visualTranspose, })[0]; 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; // console.log("🚀 ~ box:", item.abselem, box); if (box) { const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); rect.setAttributeNS(null, "x", box.x - padding + ""); rect.setAttributeNS(null, "y", box.y - padding + ""); rect.setAttributeNS(null, "width", box.width + padding * 2 + ""); rect.setAttributeNS(null, "height", box.height + padding * 2 + ""); rect.setAttributeNS(null, "fill", "rgba(0,0,0,0)"); rect.setAttributeNS(null, "stroke", "rgba(0,0,0,0)"); rect.setAttributeNS(null, "rx", "2"); rect.classList.add("abcjs-note-hover"); svg?.appendChild(rect); } } } } } console.log(measureNumber); data.selectMeasures.max = measureNumber; }; let saveTimer: any = null; const autoSave = () => { saveTimer && clearTimeout(saveTimer); saveTimer = setTimeout(() => { handleSaveMusic(false); }, 15000); }; /** * @param isProduct 是否是生成曲谱 */ const handleResetRender = () => { if (data.drawCount > 0) { abcData.synthControl.disable(true); 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(); 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++; }); }); }; /** 生成曲谱节拍器数据 */ 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]; const abcNote = getNextNote(timeNote.startChar as number); let indexStr: any = abcNote.chord?.find((n) => n.position === "left")?.name || ""; indexStr = indexStr.split(".").map((n: string) => Number(n)); if (indexStr.length === 2) { const measure = abcData.abc.measures[indexStr[0]]; // 如果小节里面有拍号,后面一直延用这个拍号,以此类推, 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) // console.log("🚀 ~ measure:", measure) const note = measure.notes[indexStr[1]]; list.push({ ...note, timeNote, abcNote, measure: { numerator, }, }); } } // 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); } }; // 高亮选中的音符 const rangeHighlight = (startChar: number) => { // console.log(data.active.endChar, abcData.visualObj.getElementFromChar(data.active.startChar)); const abcElem: AbcElem = abcData.visualObj.getElementFromChar(startChar); if (abcElem) { // abcData.visualObj.engraver.rangeHighlight(abcElem.startChar, abcElem.endChar); abcElem?.abselem?.elemset?.[0]?.classList?.add("note_selected"); } return abcElem; }; const useIndexGetNote = (indexStr: string) => { const index = data.music.indexOf(indexStr); const note = abcData.visualObj.getElementFromChar(index); return note; }; const getMeasureNotes = (measureIndex: number) => { const measure = abcData.abc.measures[measureIndex]; const notes = []; for (let k = 0; k < measure.notes.length; k++) { const indexStr = `${measureIndex}.${k}`; const note = useIndexGetNote(indexStr); notes.push(note); } 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", }, "*" ); } }; const handleClickExit = async () => { if (data.saveLoading) return; const msg = message.loading("保存中...", { duration: 0 }); await handleSaveMusic(false); setTimeout(async () => { msg.type = "success"; msg.content = "保存成功"; setTimeout(() => { msg.destroy(); }, 500); }, 300); if (data.uploadStatus !== "NO") { dialog.warning({ maskClosable: true, autoFocus: false, class: "deleteDialog saveDialog", title: "温馨提示", content: "是否更新到我的资源?", positiveText: "更新", positiveButtonProps: { type: "primary", }, negativeText: "不更新", negativeButtonProps: { type: "default", ghost: false, }, onPositiveClick: async () => { data.uploadClick = true; await handleUpdate(); }, onNegativeClick: () => { handleClose(); }, }); } else { handleClose(); } }; /** * * @param key * @param type note 音符 accidental 临时升降记号 * @returns */ const handleChange = async (params: { type: string; value: any }) => { // 最多记录30步 if (data.undoList.length > 30) { data.undoList.shift(); } data.undoList.push(cleanDeep(abcData.abc)); 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; if (type === "multiSelect") { data.multilList.forEach((item: any) => { item?.abselem?.elemset?.[0]?.classList?.add("note_selected"); }); return; } if (type === "exit") { if (!data.isSave) { clearTimeout(saveTimer); data.uploadClick = false; dialog.warning({ maskClosable: true, autoFocus: false, class: "deleteDialog saveDialog", title: "温馨提示", content: "是否保存当前曲谱?", positiveText: "保存", positiveButtonProps: { type: "primary", }, negativeText: "不保存", negativeButtonProps: { type: "default", ghost: false, }, onPositiveClick: () => { handleClickExit(); }, onNegativeClick: () => { handleClose(); }, }); return; } handleClose(); } // console.log(params, activeNote); if (type === "type") { // 设置音符类型 data.noteType = value; if (activeNote) { activeNote.noteType = value; await handleResetRender(); const abcElem: AbcElem = rangeHighlight(data.active.startChar); const active: any = abcElem ? { ...cloneDeep(abcElem), measureIndex: data.active.measureIndex, noteIndex: data.active.noteIndex, isFirstChecked: true, } : null; data.active = active; } return; } // 分割 if (type === "segno") { if (!data.active) { showToast("请先选择音符"); return; } if (!activeNote) return; activeNote.segno = activeNote.segno ? "" : value; await handleResetRender(); rangeHighlight(data.active.startChar); } // 修改音符,或者添加音符 if (type === "note") { if (data.active && data.active.el_type == "note") { const activeNote = abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null; const _values = value.split("-"); if (data.active.isFirstChecked) { activeNote.content = _values[0]; activeNote.noteType = data.noteType; 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) { // 小节内音符总时值过长,自动跳转到下一小节 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 = { ...newNote, measureIndex: data.active.measureIndex, noteIndex: data.active.noteIndex + 1, isFirstChecked: true, }; } } } } // 临时升降记号 if (type === "accidentals") { if (!data.active) { message.warning("请先选择音符"); return; } if (activeNote.content === "z") { message.warning("休止符无法添加临时升降记号"); return; } activeNote.accidental = activeNote.accidental == value ? "" : value; await handleResetRender(); const abcElem: AbcElem = rangeHighlight(data.active.startChar); const active: any = abcElem ? { ...cloneDeep(abcElem), measureIndex: data.active.measureIndex, noteIndex: data.active.noteIndex, isFirstChecked: true, } : null; data.active = active; } // 谱号 if (type === "clef") { if (data.active) { if (data.active.measureIndex === 0 && data.active.noteIndex === 0) { abcData.abc.celf = value; handleResetRender(); } else { if (!activeNote) return; activeNote.clef = `[${value}]`; await handleResetRender(); } rangeHighlight(data.active.startChar); } else { abcData.abc.celf = value; handleResetRender(); } } // 调号 if (type === "key") { if (data.active) { if (data.active.measureIndex === 0 && data.active.noteIndex === 0) { abcData.abc.key = value; abcData.abc.visualTranspose = 0; abcData.abc.visualKey = "K:C"; await handleResetRender(); } else { if (!activeNote) return; activeNote.key = `[${value}]`; await handleResetRender(); } rangeHighlight(data.active.startChar); } else { abcData.abc.key = value; abcData.abc.visualTranspose = 0; abcData.abc.visualKey = "K:C"; await handleResetRender(); } } // 拍号 if (type === "meter") { if (data.active && data.active.measureIndex !== 0) { if (!activeNote) return; const measure = abcData.abc.measures[data.active.measureIndex]; measure.meter = `[${value}]`; await handleResetRender(); const oldNote = useIndexGetNote(`${data.active.measureIndex}.${data.active.noteIndex}`); rangeHighlight(oldNote.startChar); } else { abcData.abc.meter = value; await handleResetRender(); } } // 演奏技法 if (type === "play") { if (!data.active) { message.warning("请先选择音符"); return; } if (!activeNote) return; if (activeNote.play.includes(value)) { activeNote.play = activeNote.play.filter((item) => item !== value); } else { activeNote.play.push(value); } await handleResetRender(); rangeHighlight(data.active.startChar); } // 力度标记,渐强渐弱 if (type === "dynamics") { if (!data.active) { message.info("请先选择音符"); return; } if (!activeNote) return; if (Array.isArray(value)) { if (activeNote?.dynamics) { activeNote.dynamics = ""; for (let i = 0; i < abcData.abc.measures.length; i++) { const measure = abcData.abc.measures[i]; for (let j = 0; j < measure.notes.length; j++) { const note = measure.notes[j]; if (note.dCode === activeNote.dCode) { note.dynamics = ""; } } } await handleResetRender(); } else { data.select.list = [cloneDeep(data.active)]; data.select.state = true; data.select.parmas = params; message.info("请选择结束音符"); } return; } if (activeNote.dynamics === value) { activeNote.dynamics = ""; } else { activeNote.dynamics = value; } await handleResetRender(); rangeHighlight(data.active.startChar); } // 延音 if (type === "tie") { if (!data.active) { message.info("请先选择音符"); return; } if (!activeNote) return; if (Array.isArray(value)) { if (activeNote?.tie) { activeNote.tie = ""; for (let i = 0; i < abcData.abc.measures.length; i++) { const measure = abcData.abc.measures[i]; for (let j = 0; j < measure.notes.length; j++) { const note = measure.notes[j]; if (note.tCode === activeNote.tCode) { note.tie = ""; } } } await handleResetRender(); return; } else { data.select.list = [cloneDeep(data.active)]; data.select.state = true; data.select.parmas = params; message.info("请选择结束音符"); return; } } const abcElem: any = getNextNote(data.active.endChar); if (activeNote.tieline) { activeNote.tieline = ""; } else { if (data.active.averagepitch != abcElem.averagepitch) { message.warning("必须同音高才能添加延音线"); return; } activeNote.tieline = value; } await handleResetRender(); rangeHighlight(data.active.startChar); } // 反复 if (type === "repeat") { if (!data.active) { return; } const activeMeasure = abcData.abc.measures[data.active.measureIndex] || null; if (!activeMeasure) return; if (activeMeasure.repeat === value) { activeMeasure.repeat = ""; } else { activeMeasure.repeat = value; } await handleResetRender(); rangeHighlight(data.active.startChar + value.length); } // 小节线 if (type === "barline") { if (!data.active) { return; } const activeMeasure = abcData.abc.measures[data.active.measureIndex] || null; if (!activeMeasure) return; // 如果是开始重复,就修改前一个小节的barline if (value === "|:") { const prevMeasure = abcData.abc.measures[data.active.measureIndex - 1] || null; if (!prevMeasure) return; prevMeasure.barline = prevMeasure.barline === value ? "|" : value; } else { activeMeasure.barline = activeMeasure.barline === value ? "|" : value; } await handleResetRender(); } // 速度 if (type === "speeds") { if (data.active) { if (data.active.measureIndex === 0 && data.active.noteIndex === 0) { abcData.abc.speed = value; await handleResetRender(); } else { const activeNote = abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null; if (!activeNote) return; activeNote.speed = `[${value}]`; await handleResetRender(); } rangeHighlight(data.active.startChar); } else { abcData.abc.speed = value; await handleResetRender(); } } // 附点 if (type === "dot") { if (!data.active) { showToast("请先选择音符"); return; } if (!activeNote) return; activeNote.dot = activeNote.dot ? "" : value; // activeNote.dot = activeNote.dot ? "" : NOTE_DOT[activeNote.noteType]; await handleResetRender(); rangeHighlight(data.active.startChar); } // 3连音 if (type === "slus") { const activeNote = abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null; if (!activeNote) return; activeNote.slus = activeNote.slus === value ? "" : value; await handleResetRender(); rangeHighlight(data.active.startChar); } // 移动音符 if (type === "move") { const step = value.action === "drag" ? value.step : value.action === "up" ? -1 : 1; if (data.multilList.length) { for (let i = 0; i < data.multilList.length; i++) { const item = data.multilList[i]; const note = abcData.abc.measures[item.measureIndex].notes[item.noteIndex]; note.content = moveNote(note.content, step); } await handleResetRender(); for (let i = 0; i < data.multilList.length; i++) { const item = data.multilList[i]; const abcElem = useIndexGetNote(`${item.measureIndex}.${item.noteIndex}`); abcElem?.abselem?.elemset?.[0]?.classList?.add("note_selected"); if (!abcElem?.midiPitches) return; ABCJS.synth.playEvent(abcElem.midiPitches, abcElem.midiGraceNotePitches, 1000); } return; } 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); } await handleResetRender(); data.active = null as unknown as INoteActive; } handleClearMultiSelect(); }; const handleClearMultiSelect = async () => { data.multilList = []; data.multiSelectList = []; data.multiSelect = false; }; const getNextNote = (index: number): AbcElem => { const abcElem = abcData.visualObj.getElementFromChar(index); // console.log("🚀 ~ abcElem:", index); if (abcElem?.el_type === "note") { return abcElem; } else { return getNextNote(abcElem.endChar); } }; const handleDeleteNote = () => { 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 clearSelectNote = () => { data.active = null as unknown as INoteActive; const notes = document.querySelectorAll(".abcjs-note_selected"); notes.forEach((item) => { item.classList.remove("abcjs-note_selected"); item.setAttribute("fill", "currentColor"); }); }; const handleSelectNote = async () => { const type = data.select.parmas?.type; const value = data.select.parmas?.value; const startItem = data.select.list[0]; const endItem = data.select.list[1]; // 力度标记,渐强渐弱 if (type === "dynamics") { if ( abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].dynamics || abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].dynamics ) { message.warning("已经添加了力度标记"); } else { const crescendo = Date.now() + ""; abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].dynamics = value[0]; abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].dCode = crescendo; abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].dynamics = value[1]; abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].dCode = crescendo; await handleResetRender(); } } // 连音 if (type === "tie") { const crescendo = Date.now() + ""; if (abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].tie) { const tie = abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].tie; abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].tie = value[0] + tie; } else { abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].tie = value[0]; abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].tCode = crescendo; } if (abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].tie) { const tie = abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].tie; abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].tie = tie + value[1]; } else { abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].tie = value[1]; abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].tCode = crescendo; } await handleResetRender(); } data.select.state = false; data.select.list = []; data.select.parmas = null; clearSelectNote(); message.destroyAll(); }; /** 移调 */ const handleMoveKey = async (item: (typeof ABC_DATA.key)[0]) => { // const moveData = getKeyStep(item.value, abcData.abc.key, data.moveKeyType); // console.log("🚀 ~ item:", abcData.abc.key, "=>", item.value, moveData); // 将所有的音符移调 // for (let i = 0; i < abcData.abc.measures.length; i++) { // const measure = abcData.abc.measures[i]; // for (let j = 0; j < measure.notes.length; j++) { // const note = measure.notes[j]; // if (note.content == "z") continue; // const content = moveNoteKey(note.content, moveData); // const _a = content.substring(0, 1); // if (_a === "^" || _a === "_") { // note.content = content.substring(1); // } else { // note.content = content; // } // console.log("🚀 ~ note.content:", note.content); // } // } // // console.log(abcData.abc.visualTranspose, item.step, item.value) // abcData.abc.key = item.value; // const step = // data.moveKeyType === "up" // ? item.step > 0 // ? item.step // : item.step + 12 // : data.moveKeyType === "down" // ? item.step < 0 // ? item.step // : item.step + 12 // : item.step; console.log(item); abcData.abc.visualTranspose = item.step; abcData.abc.visualKey = item.value; popup.moveKeyShow = false; if (data.playState) { abcData.synthControl.disable(true); data.playState = false; } await handleResetRender(); }; const keyDownData = reactive({ wait: false, control: false, }); const handleKeyDonw = async (e: KeyboardEvent) => { if ((e.target as HTMLElement).nodeName === "INPUT") return; if (e.key === "Control" || e.key === "Meta") { keyDownData.control = true; } if (e.key === "z" && keyDownData.control && !keyDownData.wait) { e.preventDefault(); if (data.undoList.length) { abcData.abc = cloneDeep(data.undoList[data.undoList.length - 1]); data.undoList.pop(); keyDownData.wait = true; await handleResetRender(); nextTick(() => { keyDownData.wait = false; }); } } if (e.key.toLocaleLowerCase() === "shift") { data.multiSelect = true; if (data.active?.el_type === "note") { data.multiSelectList = [cleanDeep(data.active)]; } } }; const handleKeyUp = (e: KeyboardEvent) => { if ((e.target as HTMLElement).nodeName === "INPUT") return; if (e.key.toLocaleLowerCase() === "shift") { data.multiSelect = false; } if (e.key === "Control" || e.key === "Meta") { keyDownData.control = false; } if (!data.active) return false; 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(); handleChange({ type: "move", value: { action: e.key === "ArrowUp" ? "up" : "donw" } }); return false; } }; /** 重置曲谱 */ const handleCreateSvg = () => { abcData.abc.measures = initMusic(30); handleResetRender(); }; const instruments = computed(() => { return ABCJS.synth.instrumentIndexToName.map((name, index) => ({ label: instrumentsNames[name as keyof typeof instrumentsNames], value: index, })); }); const instrumentName = computed(() => { const code = ABCJS.synth.instrumentIndexToName[abcData.synthOptions.program]; return instrumentsNames[code as keyof typeof instrumentsNames]; }); const getDetailData = async () => { const query = getQuery(); data.loading = true; const res = await api_musicSheetCreationDetail(query.id); if (res?.code == 200) { data.uploadStatus = res.data.uploadStatus || ""; data.musicId = res.data.id || ""; data.musicName = res.data.name || ""; data.creator = res.data.creator || ""; data.subjectId = res.data.subjectId || ""; let abc = "" as any; try { abc = JSON.parse(res.data.creationData); } catch (error) { console.log(error); } if (abc) { console.log("🚀 ~ abc:", abc); data.musicName = abc.title ?? data.musicName; abcData.abc.celf = abc.celf || "K:treble"; 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.visualKey = abc.visualKey || "K:C"; abcData.abc.subjectCode = abc.subjectCode || "acoustic_grand_piano"; 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); } } data.loading = false; return res; }; const setSaveLoading = (tips: boolean) => { data.saveLoading = true; if (tips) { data.saveLoadingText = true; message.loading("保存中...", { duration: 0 }); } }; const handleSaveMusic = async (tips = true) => { const query = getQuery(); abcData.abc.title = data.musicName; abcData.abc.creator = data.creator; setSaveLoading(tips); const wavUrl = await productWav(false); const pngUrl = await productPng(false); console.log("🚀 ~ pngUrl:", pngUrl); try { if (query.id) { await api_musicSheetCreationUpdate({ name: data.musicName || "未命名乐谱", creator: data.creator || "未命名乐谱", creationConfig: renderMeasures(abcData.abc, { hiddenIndex: true, showTitle: true, showCreator: true, }), creationData: JSON.stringify(cleanDeep(abcData.abc)), id: query.id, subjectId: data.subjectId, filePath: wavUrl, coverImg: pngUrl, }); } else { const res = await api_musicSheetCreationSave({ name: data.musicName || "未命名乐谱", creator: data.creator || "未命名乐谱", creationConfig: renderMeasures(abcData.abc, { hiddenIndex: true, showTitle: true, showCreator: true, }), creationData: JSON.stringify(cleanDeep(abcData.abc)), subjectId: data.subjectId, filePath: wavUrl, coverImg: pngUrl, }); if (res?.data) { const hash = location.hash.split("?"); const qs_data = qs.parse(hash[1]); qs_data.id = res.data; try { delete qs_data.config; } catch (error) { console.log("🚀 ~ error:", error); } location.hash = hash[0] + "?" + qs.stringify(qs_data); } } } catch (error) { console.log(error); } if (tips) { message.destroyAll(); message.success("保存成功"); } data.isSave = true; data.saveLoading = false; data.uploadClick = false; data.saveLoadingText = false; }; const hanldeInitCreate = () => { const query = getQuery(); const abc = decodeUrl(query.config); console.log("🚀 ~ abc:", abc); data.subjectId = abc.subjectId || ""; abcData.abc.celf = abc.celf ?? "K:treble"; abcData.abc.key = abc.key ?? "K:C"; abcData.abc.meter = abc.meter ?? "M:4/4"; abcData.abc.speed = abc.speed ?? "Q:1/4=80"; abcData.abc.visualTranspose = abc.visualTranspose ?? 0; abcData.abc.subjectCode = abc.subjectCode ?? "acoustic_grand_piano"; const _instruments = ABCJS.synth.instrumentIndexToName.indexOf(abcData.abc.subjectCode as any); abcData.synthOptions.program = _instruments > -1 ? _instruments : 0; abcData.abc.measures = initMusic(abc.measure ?? 30); data.loading = false; }; const visibility = useDocumentVisibility(); watch( () => visibility.value, (val: any) => { if (val === "hidden") { if (data.playState) { togglePlay("pause"); } } } ); onMounted(async () => { const query = getQuery(); if (query.id) { await getDetailData(); } else { hanldeInitCreate(); } 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, displayPlay: true, displayProgress: true, }); } console.log(ABCJS); await handleResetRender(); loadMiniMp3(); document.addEventListener("keyup", handleKeyUp); document.addEventListener("keydown", handleKeyDonw); window.onbeforeunload = (e) => { if (!data.isSave) { e.preventDefault(); e.returnValue = "还有没保存的"; } }; abcData.synthControl.restart(); 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); document.removeEventListener("keydown", handleKeyDonw); }); const measureComputed = computed(() => { if (!data.active) return {} as unknown as IMeasure; const activeMeasure = abcData.abc.measures[data.active.measureIndex] || {}; return activeMeasure; }); const noteComputed = computed(() => { if (!data.active) return {} as unknown as INote; const activeNote = abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || {}; return activeNote; }); /** 新建曲谱 */ const handleCreateMusic = () => { showConfirmDialog({ title: "温馨提示", message: "是否覆盖当前乐谱?", }).then(() => { handleCreateSvg(); data.active = null as unknown as INoteActive; }); }; /** 添加小节 */ const handleAddMearse = () => { for (let i = 0; i < data.addMearseNumber; i++) { if (["pre", "next"].includes(data.addMearseType)) { if (!data.active) { message.warning("请选择小节"); return; } if (data.addMearseType === "pre") { abcData.abc.measures.splice(data.active.measureIndex, 0, createMeasure()); } else if (data.addMearseType === "next") { abcData.abc.measures.splice(data.active.measureIndex + 1, 0, createMeasure()); } } else { abcData.abc.measures.push(createMeasure()); } } popup.barShow = false; handleResetRender(); }; const handleDeleteMearse = () => { if (data.deleteMearseType === "ing") { if (!data.active) { message.warning("请选择小节"); return; } abcData.abc.measures.splice(data.active.measureIndex, 1); } else if (data.deleteMearseType === "finish") { let len = abcData.abc.measures.length; for (let i = len; i > 0; i--) { if ( abcData.abc.measures[i - 1].notes.length === 1 && abcData.abc.measures[i - 1].notes[0].content === "z" ) { if (abcData.abc.measures.length === 1) { break; } abcData.abc.measures.splice(i - 1, 1); } else { break; } } } popup.mearseDeleteShow = false; handleResetRender(); }; const productPng = (isUrl = true) => { return new Promise((resolve) => { const paper = document.getElementById("exportPng"); if (!paper) return; const abc = renderMeasures(abcData.abc, { hiddenIndex: true, showTitle: true, showCreator: true, }); ABCJS.renderAbc(paper, abc, abcData.abcOptions); const svg: any = paper.children[0]?.cloneNode(true); const svgBox = paper.getBoundingClientRect(); svg.setAttribute("width", `${svgBox.width * 3}`); svg.setAttribute("height", `${svgBox.height * 3}`); const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); rect.setAttribute("x", "0"); rect.setAttribute("y", "0"); rect.setAttribute("width", `${svgBox.width * 10}`); rect.setAttribute("height", `${svgBox.height * 10}`); rect.setAttribute("fill", "#fff"); svg.prepend(rect); if (svg) { const _canvas = svg2canvas(svg.outerHTML); if (isUrl) { // document.body.appendChild(_canvas); let el: any = document.createElement("a"); // 设置 href 为图片经过 base64 编码后的字符串,默认为 png 格式 el.href = _canvas.toDataURL(); el.download = data.musicName + ".png"; // 创建一个点击事件并对 a 标签进行触发 const event = new MouseEvent("click"); el.dispatchEvent(event); } else { _canvas.toBlob(async (blob) => { const pngUrl = await api_uploadFile(blob, data.musicId + ".png"); resolve(pngUrl); }, "image/png"); } } }); }; const downPng = async () => { abcData.abc.title = `T:${data.musicName}`; abcData.abc.creator = `R:${data.creator}`; productPng(); }; const downRef = ref(); const downMidi = () => { const midi = ABCJS.synth.getMidiFile(abcData.visualObj, { chordsOff: true, midiOutputType: "link", fileName: "曲谱", }); // console.log("🚀 ~ midi:", midi) downRef.value.innerHTML = midi; downRef.value.querySelector("a").click(); }; const productWav = async (isUrl = true) => { return new Promise((resolve) => { const midiBuffer = new ABCJS.synth.CreateSynth(); midiBuffer .init({ visualObj: abcData.visualObj, options: abcData.synthOptions, }) .then(() => { midiBuffer.prime().then(async () => { if (isUrl) { downloadFile(midiBuffer.download(), (data.musicName || "曲谱") + ".wav"); } else { const blob = bufferToWave((midiBuffer as any).getAudioBuffer()); const wavurl = await api_uploadFile(blob, data.musicId + ".wav"); resolve(wavurl); } }); }); }); }; const downWav = () => { try { if (abcData.synthControl) { abcData.synthControl.download((data.musicName || "曲谱") + ".wav"); } } catch (error) { productWav(); } }; 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(); } else if (type === "midi") { downMidi(); } else if (type === "wav") { downWav(); } else if (type === "down-xml") { downXML(); } }; const handleExport = () => { data.active = null as unknown as INoteActive; const input = document.createElement("input"); input.type = "file"; input.accept = ".xml,.musicxml"; input.onchange = async (e: any) => { data.loadingAudioSrouce = true; const file = e.target.files[0]; // const formData = new FormData(); // formData.append("xmlFile", file); // const res = await api_xmlToAbc(formData); // console.log("🚀 ~ res:", res.data) const reader = new FileReader(); reader.onload = async (e: any) => { let abc = e.target.result; abc = new DOMParser().parseFromString(abc, "text/xml"); // // console.log("🚀 ~ abc:", abc); abc = (window as any).vertaal(abc, { p: "f", t: 1, u: 0, v: 3, mnum: 0 }); // console.log('abc', abc); const parseData = ABCJS.renderAbc("importRef", abc[0], { responsive: "resize" }); console.log("🚀 ~ parseData:", parseData); abcData.abc = formateAbc(parseData[0], { subjectCode: abcData.abc.subjectCode }); data.musicName = abcData.abc.title || data.musicName; data.creator = abcData.abc.creator || data.creator; await handleResetRender(); data.loadingAudioSrouce = false; }; reader.readAsText(file); }; input.click(); }; /** 设置选段小节 */ const handleSetSelectMeares = (index: number | null, type: "start" | "end") => { console.log("🚀 ~ index:", index); if (data.playState) { togglePlay("pause"); } if (type === "start") { let note = index ? useIndexGetNote(`${index - 1}.0`) : null; note = data.times.find((_note: any) => _note.startChar === note.startChar); // console.log("🚀 ~ note:", note, data.times); 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 { let note = index ? useIndexGetNote(`${index}.0`) : null; note = data.times.find((_note: any) => _note.startChar === note.startChar); // 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; } } }; const handleUpdate = async () => { if (data.saveLoading) return; if (!data.isSave) { await handleSaveMusic(); } const query = getQuery(); const res = await api_musicSheetCreationDetail(query.id); if (res.data) { if (res.data.uploadStatus !== "YES") { data.item = { ...res.data, visualObj: abcData.visualObj }; data.uploadShow = true; } else { message.info("已是最新版本"); } } }; return () => ( <>