import { computed, defineComponent, nextTick, onMounted, onUnmounted, reactive, ref } from "vue"; import ABCJS, { AbcElem, AbcVisualParams, ClickListenerAnalysis, ClickListenerDrag, SynthObjectController, } from "abcjs"; import "ABCJS/ABCJS-audio.css"; import styles from "./index.module.less"; import { showConfirmDialog } from "vant"; import Keys from "../component/keys"; import { Collapse, CollapseItem, Snackbar } from "@varlet/ui"; import { IAbc, IMeasure, INote, INoteActive } from "../types"; import { ABC_DATA, createMeasure, createNote, 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 { Dropdown, Dsubmenu, Doption, Trigger, Input, Select, Option } from "@arco-design/web-vue"; import { NButton, NDropdown, NGi, NGrid, NIcon, NInputNumber, NModal, NPopover, NPopselect, NSelect, NSpace, useMessage, } from "naive-ui"; import { LongArrowAltDown, LongArrowAltUp } from "@vicons/fa"; import { svg2canvas } from "/src/utils/svg2canvas"; import { 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 } from "../api"; 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''''", ]; const initMusic = (total: number): IMeasure[] => { return new Array(total).fill(0).map((item, index) => { return { measureNumber: index + 1, barline: "|", celf: "", key: "", repeat: "", notes: [ { accidental: "", clef: "", meter: "", content: "z", noteType: "4", play: [], key: "", speed: "", dynamics: "", dCode: "", tie: "", tCode: "", dot: "", slus: "", }, ], }; }); }; function moveNote(note: string, step: number) { var x = allPitches.indexOf(note); if (x >= 0) { const _note = allPitches[x - step]; return _note ? _note : note; } return note; } export default defineComponent({ name: "Home", setup() { const route = useRoute(); const message = useMessage(); const popup = reactive({ instrument: 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, // 设置弹窗 }); const data = reactive({ musicName: "", // 曲谱名称 musicCompose: "", // 曲谱作者 music: "", playState: false, // 播放状态 active: null as unknown as INoteActive, select: { state: false, list: [] as any[], parmas: null as any, }, isClickNote: false, /** 音符类型 */ noteType: "", selectMeasure: { start: "", end: "", }, slide: ["note", "clef", "key"], morePlay: false, // 更多演奏技法 addMearseType: "pre" as "pre" | "next" | "finish", // 添加小节类型 addMearseNumber: 1, // 添加小节数量 deleteMearseType: "ing" as "ing" | "finish", // 删除小节类型 }); 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 clickListener = ( abcElem: AbcElem, tuneNumber: number, classes: string, analysis: ClickListenerAnalysis, drag: ClickListenerDrag ) => { // console.log("🚀 ~ data.select.state:", data.select.state); 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 (data.select.state) { data.select.list.push(active); if (data.select.list.length === 1) { Snackbar("请先选择结束音符"); } if (data.select.list.length === 2) { data.select.list = data.select.list.sort((a, b) => a.startChar - b.startChar); handleSelectNote(); } return; } data.active = active; console.log( "🚀 ~ abcElem:", abcElem, data.music.substring(data.active.startChar, data.active.endChar) ); if (drag) { // console.log("🚀 ~ drag:", drag); handleMoveNote("drag", drag.step); return; } if (!abcElem?.midiPitches) return; ABCJS.synth.playEvent(abcElem.midiPitches, abcElem.midiGraceNotePitches, 1000); }; const textAreaRef = ref(); const abcData = reactive({ visualObj: null as any, midiBuffer: null as unknown as ABCJS.MidiBuffer, abcOptions: { add_classes: true, clickListener: clickListener, responsive: "resize", dragging: true, selectTypes: true, // ["note", "clef", "keySignature", "timeSignature", "dynamicDecoration"], visualTranspose: 0, wrap: { minSpacing: 2, maxSpacing: 10, preferredMeasuresPerLine: 4, }, staffwidth: 800, } as AbcVisualParams, synthControl: null as unknown as SynthObjectController, synthOptions: { program: 0, // soundFontUrl: "/soundFonts/", // soundFontUrl: "https://paulrosen.github.io/midi-js-soundfonts/MusyngKite/", }, abc: { celf: "K:treble", minUnit: "L:1/4", meter: "M:4/4", speed: "Q:1/4=60", key: "K:C", 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); } }; /** * 分词 * @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 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", ""); // }; onStart: function () { console.log("开始"); data.playState = true; var svg = document.querySelector("#paper svg"); var cursor = document.createElementNS("http://www.w3.org/2000/svg", "line"); cursor.setAttribute("class", "ABCJS-cursor"); cursor.setAttributeNS(null, "x1", "0"); cursor.setAttributeNS(null, "y1", "0"); cursor.setAttributeNS(null, "x2", "0"); 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 (ev.measureStart && ev.left === null) return; // this was the second part of a tie across a measure line. Just ignore it. var lastSelection = document.querySelectorAll("#paper svg .highlight"); for (var k = 0; k < lastSelection.length; k++) lastSelection[k].classList.remove("highlight"); for (var i = 0; i < ev.elements.length; i++) { var note = ev.elements[i]; for (var j = 0; j < note.length; j++) { note[j].classList.add("highlight"); } } 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"); var els = document.querySelectorAll("svg .highlight"); for (var i = 0; i < els.length; i++) { els[i].classList.remove("highlight"); } var 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 resetMidi = () => { abcData.synthControl = new ABCJS.synth.SynthController(); abcData.synthControl.load("#audio", cursorControl, { displayLoop: true, displayRestart: true, displayPlay: true, displayProgress: true, }); const midiBuffer = new ABCJS.synth.CreateSynth(); console.log(midiBuffer); midiBuffer.init({ visualObj: abcData.visualObj, options: abcData.synthOptions, }); abcData.synthControl .setTune(abcData.visualObj, false, { midiTranspose: abcData.abcOptions.visualTranspose, ...abcData.synthOptions, }) .then(function (response) { // console.log("Audio successfully loaded."); // console.log("🚀 ~ abcData.synthControl:", abcData.synthControl); }); }; const togglePlay = (type: "play" | "pause" | "reset") => { console.log("🚀 ~ abcData.synthControl:", abcData.synthControl); if (["play", "pause"].includes(type)) { const playBtn: HTMLElement = document.querySelector(".abcjs-midi-start.abcjs-btn")!; if (!playBtn) return; playBtn.click(); data.playState = !data.playState; } else if (type === "reset") { const resetBtn: HTMLElement = document.querySelector(".abcjs-midi-reset.abcjs-btn")!; if (!resetBtn) return; resetBtn.click(); } }; const renderSvg = () => { abcData.visualObj = ABCJS.renderAbc("paper", data.music, abcData.abcOptions)[0]; console.log("🚀 ~ visualObj:", abcData.visualObj); }; const renderBoxRect = () => { const svg = document.querySelector("#paper svg"); const padding = 4; for (let i = 0; i < abcData.visualObj.lines.length; i++) { const line = abcData.visualObj.lines[i]; 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]; // 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); } } } } } // const annotation = document.querySelectorAll("#paper .abcjs-annotation"); // annotation.forEach((n) => { // n.setAttribute("color", "rgba(0,0,0,0)"); // }) }; /** * @param isProduct 是否是生成曲谱 */ const handleResetRender = (isProduct = true) => { return new Promise((resolve) => { nextTick(() => { textAreaRef.value.value = data.music = isProduct ? renderMeasures(abcData.abc) : data.music; renderSvg(); resetMidi(); renderBoxRect(); resolve(1); }); }); }; // 高亮选中的音符 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); } return abcElem; }; /** * * @param key * @param type note 音符 accidental 临时升降记号 * @returns */ const handleChange = async (params: { type: string; value: any }) => { const type = params.type; const value = params.value; console.log(params); if (type === "type") { // 设置音符类型 data.noteType = value; return; } // 修改音符,或者添加音符 if (type === "note") { if (data.active) { if (data.active.el_type !== "note") return; const activeNote = abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null; // console.log(activeNote, data.active.isFirstChecked); const _values = value.split("-"); console.log("🚀 ~ _value:", _values); if (data.active.isFirstChecked) { activeNote.content = _values[0]; activeNote.noteType = data.noteType; if (_values[1]) { activeNote.accidental = _values[1] || ""; } } else { 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)); data.active = { ...abcElem, measureIndex: indexStr[0], noteIndex: indexStr[1], isFirstChecked: false, }; } _abcElem = rangeHighlight(abcElem.startChar); } if (!_abcElem?.midiPitches) return; ABCJS.synth.playEvent(_abcElem.midiPitches, _abcElem.midiGraceNotePitches, 1000); } else { const measureIndex = abcData.abc.measures.length - 1; const noteIndex = abcData.abc.measures[measureIndex].notes.length - 1; handleCreateNote( measureIndex, noteIndex, createNote({ content: value, noteType: data.noteType, }) ); handleResetRender(); } } // 临时升降记号 if (type === "accidentals") { if (!data.active) { Snackbar({ content: "请先选择音符", position: "top", }); return; } const activeNote = abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null; if (activeNote.content === "z") { Snackbar({ content: "休止符无法添加临时升降记号", }); return; } activeNote.accidental = value; await handleResetRender(); rangeHighlight(data.active.startChar); } // 谱号 if (type === "clef") { if (data.active) { const activeNote = abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null; if (!activeNote) return; activeNote.clef = `[${value}]`; await handleResetRender(); } else { abcData.abc.celf = value; handleResetRender(); } } // 调号 if (type === "key") { if (data.active) { const activeNote = abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null; if (!activeNote) return; activeNote.key = `[${value}]`; await handleResetRender(); } else { abcData.abc.key = value; await handleResetRender(); } } // 拍号 if (type === "meter") { if (data.active) { const activeNote = abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null; if (!activeNote) return; activeNote.meter = `[${value}]`; await handleResetRender(); } else { abcData.abc.meter = value; await handleResetRender(); } } // 演奏技法 if (type === "play") { if (!data.active) { Snackbar({ content: "请先选择音符", }); return; } const activeNote = abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null; 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 (Array.isArray(value)) { // 渐强渐弱 clearSelectNote(); data.select.list = []; data.select.state = true; data.select.parmas = params; Snackbar({ content: "请先选择开始音符", position: "top", }); return; } if (!data.active) { Snackbar({ content: "请先选择音符", }); return; } const activeNote = abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null; if (!activeNote) return; if (activeNote.dynamics === value) { activeNote.dynamics = ""; } else { activeNote.dynamics = value; } await handleResetRender(); rangeHighlight(data.active.startChar); } // 连线 if (type === "tie") { if (Array.isArray(value)) { // 渐强渐弱 clearSelectNote(); data.select.list = []; data.select.state = true; data.select.parmas = params; Snackbar({ content: "请先选择开始音符", }); return; } if (!data.active) { Snackbar({ content: "请先选择音符", }); return; } const activeNote = abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null; if (!activeNote) return; if (activeNote.tie === value) { activeNote.tie = ""; } else { activeNote.tie = value; console.log("🚀 ~ activeNote:", activeNote); } 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 = value; } else { activeMeasure.barline = 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) { Snackbar({ content: "请先选择音符", }); return; } const activeNote = abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null; if (!activeNote) return; activeNote.dot = activeNote.dot ? "" : value; 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); } }; const handleDeleteNote = () => { if (!data.active) return; if (data.active.startChar === 0) return; data.music = data.music.substring(0, data.active.startChar) + data.music.substring(data.active.endChar); 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") { 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; }; /** 移调 */ const handleMoveKey = (step: number) => { console.log("移调", step); const oldStep = abcData.abcOptions.visualTranspose || 0; abcData.abcOptions.visualTranspose = oldStep + step; popup.moveKeyShow = false; 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 (e.key === "Backspace") handleDeleteNote(); if (!data.active) return false; if (/^[A-Ga-g]$/.test(e.key)) { handleChange({ type: "note", value: e.key.toLocaleUpperCase() }); } if (["ArrowUp", "ArrowDown"].includes(e.key)) { console.log(e.key); e.preventDefault(); e.stopPropagation(); handleMoveNote(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: name, value: index })); }); const getDetailData = async () => { await api_musicSheetCreationDetail(route.query.id); }; onMounted(async () => { await getDetailData(); console.log(ABCJS); await handleResetRender(); console.log(ABCJS.extractMeasures(data.music)); document.addEventListener("keyup", handleKeyUp); }); onUnmounted(() => { document.removeEventListener("keyup", handleKeyUp); }); 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") { abcData.abc.measures.splice(abcData.abc.measures.length - 1, 1); } popup.mearseDeleteShow = false; handleResetRender(); }; const downPng = async () => { await handleResetRender(); const paper = document.getElementById("paper"); if (!paper) return; const svg: any = paper.children[0]?.cloneNode(true); const annotation = svg.querySelectorAll(".abcjs-annotation"); annotation.forEach((n: HTMLElement) => { n.remove(); }); const svgBox = paper.getBoundingClientRect(); console.log("🚀 ~ svgBox:", svgBox); svg.setAttribute("width", `${svgBox.width * 3}`); svg.setAttribute("height", `${svgBox.height * 3}`); const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); console.log("🚀 ~ svg:", svg); 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); // 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); } }; 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 downWav = () => { try { if (abcData.synthControl) { abcData.synthControl.download("曲谱.wav"); } } catch (error) { const midiBuffer = new ABCJS.synth.CreateSynth(); midiBuffer .init({ visualObj: abcData.visualObj, options: abcData.synthOptions, }) .then(() => { midiBuffer.prime().then(() => { // console.log(midiBuffer.download()); downloadFile(midiBuffer.download(), "曲谱.wav"); }); }); } }; const handleDownFile = (type: IFileBtnType) => { if (type === "png") { downPng(); } else if (type === "midi") { downMidi(); } else if (type === "wav") { downWav(); } }; const handleExport = () => { const input = document.createElement("input"); input.type = "file"; input.accept = ".xml,.musicxml"; input.onchange = (e: any) => { const file = e.target.files[0]; const reader = new FileReader(); reader.onload = (e: any) => { let abc = e.target.result; console.log("🚀 ~ abc:", abc); 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); data.music = abc[0]; handleResetRender(false); }; reader.readAsText(file); }; input.click(); }; return () => (
{ if (val === "newMusic") { handleCreateMusic(); } else if (val === "save") { } else if (["xml"].includes(val)) { handleExport(); } else if (val === "upload") { } else if (["png", "midi", "wav"].includes(val)) { handleDownFile(val); } else if (val === "print") { } }} />
文件
handleChange({ type: "dot", value: ">" })}>
" && styles.btnImgActive]}>
附点
{ABC_DATA.accidentals.map((item) => (
handleChange({ type: "accidentals", value: item.value })} >
{item.name}
))}
{ABC_DATA.tie.map((item) => (
handleChange({ type: "tie", value: item.value })} >
{item.name}
))}
{ABC_DATA.play.slice(0, 4).map((item) => (
handleChange({ type: "play", value: item.value })} >
{item.name}
))} {{ default: () => (
), content: () => (
{ABC_DATA.play.slice(4).map((item) => (
{ data.morePlay = false; handleChange({ type: "play", value: item.value }); }} >
{item.name}
))}
), }}
{ console.log(val); handleChange({ type: "slus", value: val }); }} >
连音
翻转
{{ trigger: () => (
(popup.instrument = true)}>
选择声部
), default: () => ( <>
选择声部
handleResetRender()} > ), }}
{{ trigger: () => (
移调
), default: () => ( <>
移调方式
handleMoveKey(-1)}> 向下移调 handleMoveKey(1)}> 向上移调
目标音调
{ABC_DATA.key.map((item) => (
handleChange({ type: "key", value: item.value })} >
{item.name}
))}
), }}
{{ trigger: () => (
速度调整
), default: () => handleChange(val)} />, }}
{{ trigger: () => (
谱面显示
), default: () => ( <>
乐谱大小
{ abcData.abcOptions.staffwidth = 1200; handleResetRender(); }} > 小 { abcData.abcOptions.staffwidth = 800; handleResetRender(); }} > 中 { abcData.abcOptions.staffwidth = 400; handleResetRender(); }} > 大
小节数
{ handleResetRender(); }} /> {/* (popup.staffShow = false)}> 取消 确定 */} ), }}
{{ trigger: () => (
添加小节
), default: () => ( <>
添加方式
(data.addMearseType = "pre")} > 当前小节前 (data.addMearseType = "next")} > 当前小节后 (data.addMearseType = "finish")} > 曲谱末尾
小节数
(popup.barShow = false)}> 取消 handleAddMearse()} > 确定 ), }}
{{ trigger: () => (
删除小节
), default: () => ( <>
删除方式
(data.deleteMearseType = "ing")} > 当前选中小节 (data.deleteMearseType = "finish")} > 末尾空白小节 (popup.barShow = false)}> 取消 handleDeleteMearse()} > 确定 ), }}
togglePlay("reset")} >
重播
togglePlay(data.playState ? "pause" : "play")}>
{data.playState ? "暂停" : "播放"}
选段
节拍器
(popup.settingShow = true)}>
设置
{ABC_DATA.types.map((item) => (
handleChange({ type: "type", value: item.value })} >
{item.name}
))}
handleChange({ type: "note", value: "z" })}>
休止符
{ABC_DATA.clef.map((item) => (
handleChange({ type: "clef", value: item.value })} >
{item.name}
))}
{ABC_DATA.key.map((item) => (
handleChange({ type: "key", value: item.value })} >
{item.name}
))}
{ABC_DATA.meter.map((item) => (
handleChange({ type: "meter", value: item.value })} >
{item.name}
))}
{ABC_DATA.dynamics.map((item) => (
handleChange({ type: "dynamics", value: item.value })} >
{item.name}
))}
{ABC_DATA.repeat.map((item) => (
handleChange({ type: "repeat", value: item.value })} >
{item.name}
))}
{ABC_DATA.bar.map((item) => (
{ data.morePlay = false; handleChange({ type: "barline", value: item.value }); }} >
{item.name}
))}
{/* // */}
{/* */}
handleChange(val)} /> {/*
*/}


						
						
); }, });