import { reactive } from "vue"; import { IAbc, IMeasure, INote } from "../types"; import { getImage } from "./images"; import { ABC_KEYS, ABC_NOTE_DATA } from "./noteData"; import { TuneObject } from "abcjs"; export const ABC_DATA = { /** 音符 */ types: [ { name: "全音符", value: "4", icon: "icon-quanyinfu" }, { name: "2分音符", value: "2", icon: "icon-a-2fenyinfu" }, { name: "4分音符", value: "", icon: "icon-a-4fenyinfu" }, { name: "8分音符", value: "/", icon: "icon-a-8fenyinfu" }, { name: "16分音符", value: "//", icon: "icon-a-16fenyinfu" }, { name: "32音符", value: "///", icon: "icon-a-32fenyinfu" }, ], /** 休止符 */ reset: [{ name: "休止符", value: "z", icon: "icon-a-4fenxiuzhifu" }], /** 临时升降记号 */ accidentals: [ { name: "重降号", value: "__", icon: getImage("icon_2.png") }, { name: "降号", value: "_", icon: getImage("icon_3.png") }, { name: "还原号", value: "=", icon: getImage("icon_4.png") }, { name: "升号", value: "^", icon: getImage("icon_5.png") }, { name: "重升号", value: "^^", icon: getImage("icon_6.png") }, ], /** 谱号 */ clef: [ { name: "低音谱号", value: "K:bass", icon: "icon-puhao-diyinpuhao" }, { name: "高音谱号", value: "K:treble", icon: "icon-puhao-gaoyinpuhao" }, { name: "次中音谱号", value: "K:tenor", icon: "icon-puhao-cizhongyinpuhao" }, { name: "中音谱号", value: "K:alto", icon: "icon-puhao-zhongyinpuhao" }, { name: "打击乐谱号", value: "K:perc", icon: "icon-puhao-gupu" }, ], /** 调号 */ key: [ { name: "C大调", value: "K:C", step: 0, icon: "icon-a-diaohao-cdadiaoaxiaodiao1" }, { name: "F#大调", value: "K:F#", step: 6, icon: "icon-a-diaohao-fdadiaodxiaodiao" }, { name: "F大调", value: "K:F", step: 5, icon: "icon-a-diaohao-fdadiaodxiaodiao1" }, { name: "E大调", value: "K:E", step: 4, icon: "icon-a-diaohao-edadiaocxiaodiao" }, { name: "Eb大调", value: "K:Eb", step: 3, icon: "icon-a-diaohao-ebdadiaocxiaodiao" }, { name: "D大调", value: "K:D", step: 2, icon: "icon-a-diaohao-Ddaxiaoexiaodiao" }, { name: "C#大调", value: "K:C#", step: 1, icon: "icon-a-diaohao-cdadiaoaxiaodiao" }, { name: "B大调", value: "K:B", step: -1, icon: "icon-a-diaohao-bdadiaogxiaodiao" }, { name: "Cb大调", value: "K:Cb", step: -1, icon: "icon-a-diaohao-cbdadiaoabxiaodiao" }, { name: "Db大调", value: "K:Db", step: -1, icon: "icon-a-diaohao-dbdadiaobbxiaodiao" }, { name: "Bb大调", value: "K:Bb", step: -2, icon: "icon-a-diaohao-bbdadiaogxiaodiao" }, { name: "A大调", value: "K:A", step: -3, icon: "icon-a-diaohao-Adadiaofxiaodiao" }, { name: "Ab大调", value: "K:Ab", step: -4, icon: "icon-a-diaohao-abdadiaofxiaodiao" }, { name: "G大调", value: "K:G", step: -5, icon: "icon-a-diaohao-Gdadiaoexiaodiao" }, { name: "Gb大调", value: "K:Gb", step: -6, icon: "icon-a-diaohao-gbdadiaoebxiaodiao" }, ], /** 拍号 */ meter: [ { name: "4/4", value: "M:4/4", icon: "icon-paihao-44" }, { name: "2/2", value: "M:2/2", icon: "icon-paihao-22" }, { name: "2/4", value: "M:2/4", icon: "icon-paihao-24" }, { name: "3/4", value: "M:3/4", icon: "icon-paihao-34" }, { name: "3/8", value: "M:3/8", icon: "icon-paihao-38" }, { name: "6/8", value: "M:6/8", icon: "icon-paihao-68" }, { name: "9/8", value: "M:9/8", icon: "icon-paihao-98" }, { name: "12/8", value: "M:12/8", icon: "icon-a-paihao-128" }, ], /** 演奏技法 */ play: [ { name: "加强音", value: "!marcato!", icon: getImage("icon_9.png") }, { name: "重音", value: "!>!", icon: getImage("icon_10.png") }, { name: "保持音", value: "!tenuto!", icon: getImage("icon_11.png") }, { name: "断音", value: "!wedge!", icon: getImage("icon_12.png") }, { name: "花型重复记号", value: "S", icon: "icon-fanfuyutiaoyue-sbiao" }, { name: "Coda", value: "O", icon: "icon-fanfuyutiaoyue-weisheng" }, { name: "波音", value: "P", icon: "icon-e1" }, { name: "逆波音", value: "M", icon: "icon-d1" }, { name: "换气符号(逗号)", value: "!breath!", icon: "icon-c1" }, { name: "回音", value: "!turn!", icon: "icon-b" }, // { name: "逆回音", value: "!turnx!", icon: "icon-b" }, { name: "颤音", value: "T", icon: "icon-a1" }, { name: "跳音", value: ".", icon: "icon-a-zoufajihao-duanzouhaoshang" }, { name: "延迟音记号", value: "!fermata!", icon: "icon-f1" }, ], /** 小节线 */ bar: [ { name: "单小节线", value: "|", icon: "icon-xiaojiexian-danxiaojiexian" }, { name: "双小节线", value: "||", icon: "icon-xiaojiexian-shuangxiaojiexian" }, { name: "结束线", value: "|]", icon: "icon-xiaojiexian-zhongzhixiaojiexian" }, { name: "重复线开始", value: "|:", icon: "icon-a-xiaojiexian-zuoqishifanfuhao" }, { name: "重复线结束", value: ":|", icon: "icon-a-xiaojiexian-youzhongzhifanfuhao" }, { name: "双重复", value: "::", icon: "icon-xiaojiexian-jieshuyuqishifanfubiaozhi" }, ], /** 连线 */ tie: [ { name: "延音线", value: "-", icon: getImage("icon_7.png") }, // 延音必须同音高 { name: "连音线", value: ["(", ")"], icon: getImage("icon_8.png") }, // 需要是音符和结束音符,最低两个音符 ], /** 8度线 */ octave: [ // 暂不支持 { name: "高8度开始", value: ["!8va(!", "!8va)!"] }, { name: "低8度", value: ["!8vb(!", "!8vb)!"] }, ], /** 力度记号 */ dynamics: [ { name: "极弱", value: "!ppp!", icon: "icon-lidujihao-ppp" }, { name: "很弱", value: "!pp!", icon: "icon-lidujihao-pp" }, { name: "弱", value: "!p!", icon: "icon-lidujihao-p" }, { name: "中弱", value: "!mp!", icon: "icon-lidujihao-mp" }, { name: "中强", value: "!mf!", icon: "icon-lidujihao-mf" }, { name: "强", value: "!f!", icon: "icon-lidujihao-f" }, { name: "很强", value: "!ff!", icon: "icon-lidujihao-ff" }, { name: "极强", value: "!fff!", icon: "icon-lidujihao-fff" }, { name: "渐强", value: ["!<(!", "!<)!"], icon: "icon-lidujihao-jianqianghao" }, // 需要是音符范围,最低两个音 { name: "渐弱", value: ["!>(!", "!>)!"], icon: "icon-lidujihao-jianruohao" }, //需要是音符范围,最低两个音 ], repeat: [ { name: "第一跳跃", value: "1", icon: "icon-fanfuyutiaoyue-diyitiaoyuehao" }, { name: "第二跳跃", value: "2", icon: "icon-fanfuyutiaoyue-di2kaifangtiaoyuehao" }, // { name: "重复3房", value: "3", icon: "" }, // { name: "重复4房", value: "4", icon: "" }, ], speeds: [ { name: "60", value: "Q:1/4=60", icon: "" }, { name: "70", value: "Q:1/4=70", icon: "" }, { name: "80", value: "Q:1/4=80", icon: "" }, { name: "90", value: "Q:1/4=90", icon: "" }, { name: "100", value: "Q:1/4=100", icon: "" }, { name: "120", value: "Q:1/4=120", icon: "" }, ], slus: [ { name: "3连音", value: "(3", icon: "" }, { name: "4连音", value: "(4", icon: "" }, { name: "5连音", value: "(5", icon: "" }, { name: "6连音", value: "(6", icon: "" }, { name: "7连音", value: "(7", icon: "" }, ], /** 3连音等多连音 * 1. 将连音的音符小括号起来,并在括号前加上数字表示连音的音符数 */ }; export const settings = reactive({ /** 光标跟随 音符, 节拍 */ cursorType: "note" as "note" | "beat", }); export const createNote = (options: Partial): INote => { return { accidental: options.accidental || "", content: options.content || "", noteType: options.noteType || "", clef: options.clef || "", play: options.play || [], key: options.key || "", speed: options.speed || "", dynamics: options.dynamics || "", dCode: options.dCode || "", tie: options.tie || "", tCode: options.tCode || "", dot: options.dot || "", slus: options.slus || "", tieline: options.tieline || "", segno: options.segno || "", }; }; export const createMeasure = (): IMeasure => { return { notes: [ createNote({ content: "z", noteType: "4", }), ], barline: "|", repeat: "", measureNumber: 0, celf: "", key: "", meter: "", }; }; interface IRenderMeasuresOption { /** 是否生成index */ hiddenIndex?: boolean; showTitle?: boolean; showCreator?: boolean; jianpu?: boolean; } /** * 生成小节 * @param abc * * @returns */ export const renderMeasures = (abc: IAbc, option?: IRenderMeasuresOption) => { // console.log("🚀 ~ abc:", abc) let wrap = 1; let text = `X:1\n`; if (option?.jianpu){ text += '%%jianpu 1 \n' } if (option?.showTitle) { abc.title && (text += `T:${abc.title}` + "\n"); } if (option?.showCreator) { abc.creator && (text += `C:${abc.creator}` + "\n"); } if (!option?.hiddenIndex) { text += "%%barnumbers 1" + "\n"; } abc.celf && (text += abc.celf + "\n"); abc.meter && (text += abc.meter + "\n"); abc.minUnit && (text += abc.minUnit + "\n"); abc.speed && (text += abc.speed + "\n"); if (abc.key) { text += abc.key + " "; // text += "style=harmonic"; if (abc.isrhythm === 'rhythm'){ text += "style=x"; } // text += "style=x"; // text += "style=triangle"; text += "\n"; } // text += "V:1 style=jianpu" + "\n"; const measures = abc.measures; for (let i = 0; i < measures.length; i++) { const measure = measures[i]; text += measure.repeat ?? ""; // 重复 text += measure.meter ?? ""; // 拍号 for (let j = 0; j < measure.notes.length; j++) { const note = measure.notes[j]; const playStr = note.play?.join("") ?? ""; text += note.clef ?? ""; // 谱号 text += note.key ?? ""; // 调号 text += note.speed ?? ""; // 速度 text += note.slus ?? ""; // 3连音 if (note.tie?.includes("(")) { // 连音线 前 text += note.tie ?? ""; } if (!option?.hiddenIndex) { text += `"<${i + "." + j}"`; // 音符 id } text += playStr ?? ""; // 演奏技法 text += note.dynamics ?? ""; // 力度符号 text += note.accidental ?? ""; // 临时升降记号 if (abc.isrhythm === 'rhythm'){ text += "B"; } else { text += note.content ?? ""; // 音符 } // 音符时值 text += note.noteType ?? ""; text += note.dot ?? ""; // 点 text += note.tieline ?? ""; // 延音 if (note.tie?.includes(")")) { // 连音线 后 text += note.tie ?? ""; } text += note.segno ?? ""; // 分割 } // let _i = i + 1; // if (!option?.hiddenIndex) { // text += `"<${_i}"`; // } text += measure.barline ?? ""; if (wrap % 4 === 0) { text += "\n"; } wrap++; } // console.log(text) return text; }; export const getKeyStep = (key: string, oldKey: string, keyType: "inset" | "up" | "down") => { let step = 0; const _key = ABC_KEYS[oldKey][key]; if (keyType === "down") { step = _key.down; } else if (keyType === "up") { step = _key.up; } else { step = Math.abs(_key.up) > Math.abs(_key.down) ? _key.down : _key.up; } return { step, move: _key.move }; }; export const moveNoteKey = (note: string, moveData: { step: number; move: number }) => { let x = -1; for (let i = 0; i < ABC_NOTE_DATA.length; i++) { const notes = ABC_NOTE_DATA[i]; if (Array.isArray(notes) && notes.includes(note)) { x = i; break; } if (note === notes) { x = i; break; } } console.log(note, moveData.step, x); if (x >= 0) { const _note = ABC_NOTE_DATA[x + moveData.step]; if (Array.isArray(_note)) { return _note[moveData.move]; } else { return _note ? _note : note; } } return note; }; const formateGetData = { getNoteType: (duration: number) => { const type = 0.25 / duration; // console.log(type, duration); const noteType = [ { name: 0.25, value: "4" }, { name: 0.5, value: "2" }, { name: 1, value: "" }, { name: 2, value: "/" }, { name: 4, value: "//" }, { name: 8, value: "///" }, ]; let _duration = noteType.find((n) => n.name === type)?.value || ""; const noteDuration: any = { "0.046875": "1/6", "0.09375": "1/3", "0.1875": "2/3", "0.375": "3/2", "0.75": "3", }; if (_duration === "") { _duration = noteDuration[duration] || ""; } return _duration; }, getSegno(ele: any, nextEle: any) { let segno = " "; if (nextEle && ele) { // console.log("🚀 ~ nextEle:", nextEle) if (nextEle?.abselem?.beam?.elems && Array.isArray(nextEle.abselem.beam.elems)) { const _ele = nextEle.abselem.beam.elems.find((n: any) => n.abcelem.startChar === ele.startChar); // console.log("🚀 ~ elems:", _ele) if (_ele) { segno = ""; } } } return segno; }, }; export const formateAbc = (visualObj: TuneObject, option: any) => { let speed = visualObj?.metaText?.tempo?.bpm ? visualObj.metaText.tempo.bpm : visualObj.getBpm(); const abc = { celf: "K:treble", minUnit: "L:1/4", meter: "M:4/4", speed: `Q:1/4=${speed}`, key: "K:C", visualTranspose: 0, subjectCode: option.subjectCode ?? "acoustic_grand_piano", title: visualObj?.metaText?.title ?? "", creator: visualObj?.metaText?.composer ?? "", }; const list = []; let notes = []; let measureIndex = 0; const get_accidental = (noteItem: INote) => { let accidental = ""; if (noteItem.content.includes("_")) { accidental = "_"; } if (noteItem.content.includes("__")) { accidental = "__"; } if (noteItem.content.includes("=")) { accidental = "="; } if (noteItem.content.includes("^")) { accidental = "^"; } if (noteItem.content.includes("^^")) { accidental = "^^"; } return accidental; }; for (let i = 0; i < visualObj.lines.length; i++) { const line = visualObj.lines[i]; if (line.staff) { for (let j = 0; j < line.staff.length; j++) { const staff = line.staff[j]; if (i === 0) { if (staff.clef) { abc.celf = `K:${staff.clef.type}`; } if (staff.key) { abc.key = `K:${staff.key.root}${staff.key.acc}`; } if (staff.meter?.value?.[0]) { abc.meter = `M:${staff.meter.value[0].num}/${staff.meter.value[0].den}`; } } if (staff.voices) { let measure = { notes: [] as INote[], barline: "|", repeat: "", measureNumber: measureIndex, celf: "", key: "", meter: "", }; for (let k = 0; k < staff.voices.length; k++) { const voice = staff.voices[k]; for (let l = 0; l < voice.length; l++) { const element = voice[l]; const nextElement = voice[l + 1]; if (element.el_type === "bar") { measureIndex++; list.push(measure); notes = []; measure = { notes: [] as INote[], barline: "|", repeat: "", measureNumber: measureIndex, celf: "", key: "", meter: "", }; } if (element.el_type === "note") { // const abcEle = visualObj.getElementFromChar(element.startChar); // console.log("🚀 ~ abcEle:", element); let noteItem = { clef: "", //// 谱号 key: "", // 调号 speed: "", // 速度 slus: "", // 3连音 tie: "", // 连音线 前,连音线 后 content: "", // 音符 noteType: formateGetData.getNoteType(element.duration), // 音符时值 play: [], dynamics: "", // 力度符号 accidental: "", // 临时升降记号 dot: "", //附点 tieline: "", // 延音线 segno: formateGetData.getSegno(element, nextElement), // 分割 }; if (element.rest) { noteItem.content = "z"; } else { noteItem.content = element.pitches?.[0]?.name ?? ""; } noteItem.accidental = get_accidental(noteItem as any); if (noteItem.accidental) { noteItem.content = noteItem.content.replace(noteItem.accidental, ""); } const note = createNote(noteItem); measure.notes.push(note); } } } } } } } console.log(measureIndex, list); return { ...abc, measures: list, }; };