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, 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, 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 { Dropdown, Dsubmenu, Doption, Trigger, Input, Select, Option } from "@arco-design/web-vue"; import { NButton, NDropdown, NGi, NGrid, NIcon, NInput, NInputNumber, NModal, NPopover, NPopselect, NSelect, NSpace, NSpin, useMessage, } from "naive-ui"; import { LongArrowAltDown, LongArrowAltUp, GripLinesVertical } 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, api_musicSheetCreationUpdate } 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''''", ]; export 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: "", tieline: "", segno: "", }, ], }; }); }; 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, 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, // 设置弹窗 }); const data = reactive({ isSave: false, musicId: "", 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: "", selectMeasure: { start: "", end: "", }, slide: ["note", "clef", "key"], morePlay: false, // 更多演奏技法 addMearseType: "pre" as "pre" | "next" | "finish", // 添加小节类型 addMearseNumber: 1, // 添加小节数量 deleteMearseType: "ing" as "ing" | "finish", // 删除小节类型 loadingAudioSrouce: false, // 加载音频资源 /** 移调类型 */ moveKeyType: "inset" as "inset" | "up" | "down", // 移调类型 }); 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) { 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; console.log( "🚀 ~ abcElem:", abcElem, data.music.substring(data.active.startChar, data.active.endChar) ); if (data.active?.el_type === "note") { const totalTime = (abcData.synthControl as any).visualObj.getTotalTime(); if (totalTime) { const progress = (data.active as any).currentTrackMilliseconds / 1000 / totalTime; // console.log("🚀 ~ data.active:", data.active, progress); (abcData.synthControl as any).seek(progress); } } // if (abcElem.el_type === "tempo") { // abcData.visualObj.engraver.rangeHighlight(abcElem.startChar, abcElem.endChar); // } if (drag && drag.step) { 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: ["note"], 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", visualTranspose: 0, 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); } }; /** * 分词 * @param str 字符串 * @returns * @description */ const tokenize = (str: string) => { const arr = str.split(/(!.+?!|".+?")/); let output: string[] = []; for (let i = 0; i < arr.length; i++) { const token = arr[i]; if (token.length > 0) { if (token[0] !== '"' && token[0] !== "!") { const arr2 = arr[i].split(/([A-Ga-gz][,']*)/); output = output.concat(arr2); } else output.push(token); } } return output; }; const hideCursor = () => { const cursor = document.querySelector("#paper svg .ABCJS-cursor"); if (cursor) { cursor.setAttribute("x1", "0"); cursor.setAttribute("x2", "0"); cursor.setAttribute("y1", "0"); cursor.setAttribute("y2", "0"); } }; 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) => { 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. 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 resetMidi = (useActive = false) => { data.loadingAudioSrouce = true; const midiBuffer = new ABCJS.synth.CreateSynth(); // console.log(midiBuffer); midiBuffer .init({ visualObj: abcData.visualObj, options: { ...abcData.synthOptions, midiTranspose: abcData.abc.visualTranspose }, }) .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."); // console.log("🚀 ~ abcData.synthControl:", abcData.synthControl); }) .catch((err) => { console.log(err); }); }); }; const togglePlay = (type: "play" | "pause" | "reset") => { console.log("🚀 ~ abcData.synthControl:", abcData.synthControl); if (type === "play") { abcData.synthControl.play(); data.playState = true; } else if (type === "pause") { abcData.synthControl.play(); data.playState = false; hideCursor(); } else { abcData.synthControl.restart(); } }; const renderSvg = () => { abcData.visualObj = ABCJS.renderAbc("paper", data.music, { ...abcData.abcOptions, visualTranspose: abcData.abc.visualTranspose, })[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(() => { data.music = isProduct ? renderMeasures(abcData.abc) : data.music; renderSvg(); resetMidi(); renderBoxRect(); resolve(1); textAreaRef.value && (textAreaRef.value.value = data.music); }); }); }; // 高亮选中的音符 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; }; 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; }; /** * * @param key * @param type note 音符 accidental 临时升降记号 * @returns */ const handleChange = async (params: { type: string; value: any }) => { const type = params.type; const value = params.value; const activeNote = abcData.abc.measures[data.active?.measureIndex]?.notes[data.active?.noteIndex] || null; console.log(params, activeNote); 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] || ""; } } else { const notes = getMeasureNotes(data.active.measureIndex); const duration = notes.map((n) => n.duration).reduce((a, b) => a + b); if (duration >= 1) { message.warning("小节内音符总时值过长"); return; } handleCreateNote( data.active.measureIndex, data.active.noteIndex, createNote({ content: _values[0], noteType: data.noteType, accidental: _values[1] || "", }) ); } await handleResetRender(); let _abcElem: AbcElem; if (data.active.isFirstChecked) { data.active.isFirstChecked = false; _abcElem = rangeHighlight(data.active.startChar); } else { const oldElem: AbcElem = abcData.visualObj.getElementFromChar(data.active.startChar); const abcElem: AbcElem = abcData.visualObj.getElementFromChar(oldElem.endChar); if (abcElem) { let indexStr: any = abcElem.chord?.find((n) => n.position === "left")?.name || ""; indexStr = indexStr.split(".").map((n: string) => Number(n)); data.active = { ...abcElem, measureIndex: indexStr[0], noteIndex: indexStr[1], isFirstChecked: false, }; const beam = (abcElem.abselem as any).beam; if (beam) { const elems: AbcElem[] = beam.elems; if (elems.length) { const beatDuration = abcData.visualObj.getBeatLength(); const beamLength = elems.map((n) => n.duration).reduce((a, b) => a + b); if (beamLength >= beatDuration) { abcData.abc.measures[data.active.measureIndex].notes[data.active.noteIndex].segno = " "; await handleResetRender(); } } } } _abcElem = rangeHighlight(abcElem.startChar); } if (!_abcElem?.midiPitches) return; ABCJS.synth.playEvent(_abcElem.midiPitches, _abcElem.midiGraceNotePitches, 1000); } } // 临时升降记号 if (type === "accidentals") { if (!data.active) { showToast("请先选择音符"); return; } if (activeNote.content === "z") { showToast("休止符无法添加临时升降记号"); 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 (!activeNote) return; activeNote.clef = `[${value}]`; await handleResetRender(); } else { abcData.abc.celf = value; handleResetRender(); } } // 调号 if (type === "key") { if (data.active) { if (!activeNote) return; activeNote.key = `[${value}]`; await handleResetRender(); } else { abcData.abc.key = value; await handleResetRender(); } } // 拍号 if (type === "meter") { if (data.active) { if (!activeNote) return; activeNote.meter = `[${value}]`; await handleResetRender(); } else { abcData.abc.meter = value; await handleResetRender(); } } // 演奏技法 if (type === "play") { if (!data.active) { showToast("请先选择音符"); 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 = 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) { showToast("请先选择音符"); 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 getNextNote = (index: number): AbcElem => { const abcElem = abcData.visualObj.getElementFromChar(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 = (item: (typeof ABC_DATA.key)[0]) => { const step = getKeyStep(item.value, abcData.abc.key, data.moveKeyType); console.log("🚀 ~ item:", abcData.abc.key, "=>", item.value, step); // 将所有的音符移调 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, step); 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; 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 (!data.active) return false; console.log(e.key); if (e.key === "Backspace") handleDeleteNote(); if (/^[A-Ga-g]$/.test(e.key)) { handleChange({ type: "note", value: e.key.toLocaleUpperCase() }); } if (["ArrowUp", "ArrowDown"].includes(e.key)) { e.preventDefault(); e.stopPropagation(); handleMoveNote(e.key === "ArrowUp" ? "up" : "donw"); 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 () => { const res = await api_musicSheetCreationDetail(route.query.id); if (res?.code == 200) { data.musicId = res.data.id || ""; data.musicName = res.data.name || ""; data.creator = res.data.creator || ""; let abc = "" as any; try { abc = JSON.parse(res.data.creationData); } catch (error) { console.log(error); } if (abc) { console.log("🚀 ~ abc:", abc); abcData.abc.celf = abc.celf || "K:treble"; abcData.abc.key = abc.key.value || "K:C"; abcData.abc.meter = abc.meter.value || "M:4/4"; abcData.abc.speed = abc.speed || "Q:1/4=60"; 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 = abc.measures || initMusic(30); console.log("🚀 ~ abcData.abc:", abcData.abc); } } }; const handleSaveMusic = async () => { await api_musicSheetCreationUpdate({ name: data.musicName, creator: data.creator, creationConfig: data.music, creationData: JSON.stringify(abcData.abc), id: data.musicId, subjectId: 3, }); message.success("保存成功"); data.isSave = true; }; onMounted(async () => { await getDetailData(); if (ABCJS.synth.supportsAudio()) { abcData.synthControl = new ABCJS.synth.SynthController(); abcData.synthControl.load("#audio", cursorControl, { displayLoop: true, displayRestart: true, displayPlay: true, displayProgress: true, }); } console.log(ABCJS); await handleResetRender(); document.addEventListener("keyup", handleKeyUp); window.onbeforeunload = (e) => { if (!data.isSave) { e.preventDefault(); e.returnValue = "还有没保存的"; } }; }); 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 () => (
e.stopPropagation()}>
{ if (val === "newMusic") { handleCreateMusic(); } else if (val === "save") { handleSaveMusic(); } 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}
))}
handleChange({ type: "tie", value: ABC_DATA.tie[0].value })} >
{ABC_DATA.tie[0].name}
handleChange({ type: "tie", value: ABC_DATA.tie[1].value })} >
{ABC_DATA.tie[1].name}
{ABC_DATA.play.slice(0, 4).map((item) => (
handleChange({ type: "play", value: item.value })} >
{item.name}
))} {{ trigger: () => (
), default: () => ( {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: () => ( <>
选择声部
{ abcData.synthControl.disable(true); data.playState = false; resetMidi(true); popup.selectSubjectShow = false; }} > ), }}
{{ trigger: () => (
移调
), default: () => ( <>
移调方式
(data.moveKeyType = "inset")} > 最靠近 (data.moveKeyType = "down")} > 向下移调 (data.moveKeyType = "up")} > 向上移调
目标音调
{ABC_DATA.key .sort((a, b) => b.step - a.step) .map((item) => (
handleMoveKey(item)} >
{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.mearseDeleteShow = 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" })}>
休止符
handleChange({ type: "segno", value: " " })}>
{/* */}
分割
{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.slice(0, 8).map((item) => (
handleChange({ type: "dynamics", value: item.value })} >
{item.name}
))}
handleChange({ type: "dynamics", value: ABC_DATA.dynamics[8].value })} >
{ABC_DATA.dynamics[8].name}
handleChange({ type: "dynamics", value: ABC_DATA.dynamics[9].value })} >
{ABC_DATA.dynamics[9].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}
))}
e.stopPropagation()} v-model:value={data.musicName} placeholder="乐谱名称" />
e.stopPropagation()} v-model={data.creator} placeholder="曲谱作者" />
handleChange(val)} /> {/*
*/}


						
						
); }, });