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, 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 { 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";
import instrumentsNames from "/src/constant/instrmentsNames.json";
import { ALL_NOTES, NOTE_DOT } from "./noteData";
import { Close } from "@vicons/ionicons5";
import { UseDraggable } from "@vueuse/components";
import { getQuery } from "/src/utils/queryString";
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 dialog = useDialog();
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({
drawCount: 0,
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: "",
/** 选择小节范围 */
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", // 移调类型
});
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:", 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 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: "/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);
}
};
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");
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);
},
onBeat: function (beatNumber: any, totalBeats: any, totalTime: any) {},
onEvent: (ev: any) => {
// console.log("🚀 ~ ev:", ev);
if (!data.playState) return;
if (ev.measureStart && ev.left === null) return; // this was the second part of a tie across a measure line. Just ignore it.
if (popup.selectMearesShow) {
const startTime = data.selectMeasures.startNote?.currentTrackMilliseconds || 0;
const endNote: any = data.selectMeasures.endNote ? data.selectMeasures.endNote : null;
// console.log("🚀 ~ endNote:", ev.milliseconds , endNote.currentTrackMilliseconds)
if (ev.milliseconds < startTime || (endNote && ev.milliseconds > endNote.currentTrackMilliseconds)) {
const totalTime = (abcData.synthControl as any).visualObj.getTotalTime();
if (totalTime) {
const progress = startTime / 1000 / totalTime;
nextTick(() => {
(abcData.synthControl as any).seek(progress);
});
}
}
}
var cursor = document.querySelector("#paper svg .ABCJS-cursor");
if (cursor) {
cursor.setAttribute("x1", ev.left + ev.width / 2);
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 = (type: "play" | "pause" | "reset") => {
if (type === "play") {
abcData.synthControl.play();
data.playState = true;
} else if (type === "pause") {
abcData.synthControl.play();
data.playState = false;
hideCursor();
} else {
abcData.synthControl.restart();
}
// 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;
};
/**
* @param isProduct 是否是生成曲谱
*/
const handleResetRender = () => {
return new Promise((resolve) => {
nextTick(() => {
data.music = renderMeasures(abcData.abc);
renderSvg();
resetMidi(data.drawCount > 0 ? true : false);
renderBoxRect();
resolve(1);
textAreaRef.value && (textAreaRef.value.value = data.music);
data.drawCount++;
// const times = new ABCJS.TimingCallbacks(abcData.visualObj);
// console.log("🚀 ~ times:", times)
});
});
};
// 高亮选中的音符
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 }) => {
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 === "exit") {
// 退出时先保存 不提示
await handleSaveMusic(false);
// 判断是否在应用中
if (window.matchMedia("(display-mode: standalone)").matches) {
window.onbeforeunload = null;
window.parent.postMessage(
{
api: "notation_exit",
},
"*"
);
} else {
dialog.warning({
autoFocus: false,
class: "deleteDialog",
title: "退出制谱",
content: () =>
是否退出制谱功能?
,
positiveText: "取消",
positiveButtonProps: {
type: "default",
},
negativeText: "确定",
negativeButtonProps: {
type: "primary",
ghost: false,
},
onPositiveClick: () => {},
onNegativeClick: async () => {
window.onbeforeunload = null;
window.close();
},
});
}
}
// 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 (!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();
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 = 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;
}
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._step ? value._step : value.action === "up" ? -1 : 1;
if (!activeNote) return;
activeNote.content = moveNote(activeNote.content, step);
// arr now contains elements that are either a chord, a decoration, a note name, or anything else. It can be put back to its original string with .join("").
await handleResetRender();
const _abcElem = rangeHighlight(data.active.startChar);
if (!_abcElem?.midiPitches) return;
ABCJS.synth.playEvent(_abcElem.midiPitches, _abcElem.midiGraceNotePitches, 1000);
}
// 删除音符
if (type === "delete") {
if (!data.active) return;
if (data.active.startChar === 0) return;
abcData.abc.measures[data.active.measureIndex].notes.splice(data.active.noteIndex, 1);
if (abcData.abc.measures[data.active.measureIndex].notes.length === 0) {
abcData.abc.measures.splice(data.active.measureIndex, 1);
}
handleResetRender();
data.active = null as unknown as INoteActive;
}
};
const getNextNote = (index: number): AbcElem => {
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 = 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;
abcData.abc.visualTranspose = item.step;
popup.moveKeyShow = false;
if (data.playState) {
abcData.synthControl.disable(true);
data.playState = false;
}
await handleResetRender();
};
const handleKeyUp = (e: KeyboardEvent) => {
if (!data.active) return false;
console.log(e.key);
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 getDetailData = async () => {
const query: any = getQuery();
const res = await api_musicSheetCreationDetail(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 (tips = true) => {
await api_musicSheetCreationUpdate({
name: data.musicName,
creator: data.creator,
creationConfig: data.music,
creationData: JSON.stringify(abcData.abc),
id: data.musicId,
subjectId: 3,
});
if (tips) {
message.success("保存成功");
}
data.isSave = true;
};
onMounted(async () => {
await getDetailData();
loadMiniMp3();
if (ABCJS.synth.supportsAudio()) {
abcData.synthControl = new ABCJS.synth.SynthController();
// console.log("🚀 ~ abcData.synthControl:", abcData.synthControl)
abcData.synthControl.load("#audio", cursorControl, {
displayLoop: true,
displayRestart: true,
displayPlay: true,
displayProgress: true,
});
}
console.log(ABCJS);
await handleResetRender();
document.addEventListener("keyup", handleKeyUp);
window.onbeforeunload = (e) => {
if (!data.isSave) {
e.preventDefault();
e.returnValue = "还有没保存的";
}
};
abcData.synthControl.restart();
// formateAbc(abcData.visualObj);
const selectMearesBtn = document.querySelector("#selectMearesBtn");
if (selectMearesBtn) {
const rect = selectMearesBtn.getBoundingClientRect();
data.selectMeasures.x = document.body.clientWidth - 320;
data.selectMeasures.y = rect.top + 70;
data.selectMeasures.state = true;
}
});
onUnmounted(() => {
document.removeEventListener("keyup", handleKeyUp);
});
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 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();
};
/** 设置选段小节 */
const handleSetSelectMeares = (index: number | null, type: "start" | "end") => {
console.log("🚀 ~ index:", index);
if (type === "start") {
const note = index ? useIndexGetNote(`${index - 1}.0`) : null;
// console.log("🚀 ~ note:", note);
data.selectMeasures.start = index ? index - 1 : 0;
data.selectMeasures.startNote = note;
if (data.selectMeasures.start && data.selectMeasures.end && data.selectMeasures.end < data.selectMeasures.start) {
data.selectMeasures.end = 0;
data.selectMeasures.endNote = null;
}
} else {
const note = index ? useIndexGetNote(`${index - 1}.${abcData.abc.measures[index - 1]?.notes.length - 1}`) : null;
// console.log("🚀 ~ note:", note);
data.selectMeasures.end = index ? index - 1 : 0;
data.selectMeasures.endNote = note;
if (data.selectMeasures.start && data.selectMeasures.end && data.selectMeasures.start > data.selectMeasures.end) {
// console.log(data.selectMeasures.start, data.selectMeasures.end);
data.selectMeasures.start = 0;
data.selectMeasures.startNote = null;
}
}
};
return () => (
e.stopPropagation()}>
handleChange({ type: "exit", value: "exit" })}>
退出
{
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") {
}
// else if (val === "exit") {
// }
}}
/>
文件
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}
))}
{
console.log(val);
handleChange({ type: "slus", value: val });
}}
>
togglePlay(data.playState ? "pause" : "play")}>
{data.playState ? "暂停" : "播放"}
(popup.selectMearesShow = !popup.selectMearesShow)}>
选段
(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.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}
))}
{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}
))}
e.stopPropagation()} v-model:value={data.musicName} placeholder="曲谱名称" />
e.stopPropagation()} v-model:value={data.creator} placeholder="曲谱作者" />
handleChange(val)} />
{/* */}
{data.selectMeasures.state && (
e.stopPropagation()}>
输入小节范围
(popup.selectMearesShow = false)}>
} />
handleSetSelectMeares(val, "start")}>-
handleSetSelectMeares(val, "end")}>
togglePlay(data.playState ? "pause" : "play")}>
)}
);
},
});