index.tsx 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785
  1. import { computed, defineComponent, nextTick, onMounted, onUnmounted, reactive, ref } from "vue";
  2. import ABCJS, {
  3. AbcElem,
  4. AbcVisualParams,
  5. ClickListenerAnalysis,
  6. ClickListenerDrag,
  7. SynthObjectController,
  8. } from "abcjs";
  9. import "ABCJS/ABCJS-audio.css";
  10. import styles from "./index.module.less";
  11. import { showConfirmDialog } from "vant";
  12. import Keys from "../component/keys";
  13. import { Collapse, CollapseItem, Snackbar } from "@varlet/ui";
  14. import { IAbc, IMeasure, INote, INoteActive } from "../types";
  15. import { ABC_DATA, createMeasure, createNote, renderMeasures } from "./runtime";
  16. import TheIcon from "/src/components/The-icon";
  17. import { cloneDeep } from "lodash";
  18. import TheSpeed from "./component/the-speed";
  19. import { getImage } from "./images";
  20. import { Dropdown, Dsubmenu, Doption, Trigger, Input, Select, Option } from "@arco-design/web-vue";
  21. import {
  22. NButton,
  23. NDropdown,
  24. NGi,
  25. NGrid,
  26. NIcon,
  27. NInput,
  28. NInputNumber,
  29. NModal,
  30. NPopover,
  31. NPopselect,
  32. NSelect,
  33. NSpace,
  34. useMessage,
  35. } from "naive-ui";
  36. import { LongArrowAltDown, LongArrowAltUp } from "@vicons/fa";
  37. import { svg2canvas } from "/src/utils/svg2canvas";
  38. import { downloadFile } from "/src/utils";
  39. import FileBtn, { IFileBtnType } from "./component/file-btn";
  40. import TheSetting from "./component/the-setting";
  41. import { useRoute } from "vue-router";
  42. import { api_musicSheetCreationDetail, api_musicSheetCreationUpdate } from "../api";
  43. const allPitches = [
  44. "C,,,,",
  45. "D,,,,",
  46. "E,,,,",
  47. "F,,,,",
  48. "G,,,,",
  49. "A,,,,",
  50. "B,,,,",
  51. "C,,,",
  52. "D,,,",
  53. "E,,,",
  54. "F,,,",
  55. "G,,,",
  56. "A,,,",
  57. "B,,,",
  58. "C,,",
  59. "D,,",
  60. "E,,",
  61. "F,,",
  62. "G,,",
  63. "A,,",
  64. "B,,",
  65. "C,",
  66. "D,",
  67. "E,",
  68. "F,",
  69. "G,",
  70. "A,",
  71. "B,",
  72. "C",
  73. "D",
  74. "E",
  75. "F",
  76. "G",
  77. "A",
  78. "B",
  79. "c",
  80. "d",
  81. "e",
  82. "f",
  83. "g",
  84. "a",
  85. "b",
  86. "c'",
  87. "d'",
  88. "e'",
  89. "f'",
  90. "g'",
  91. "a'",
  92. "b'",
  93. "c''",
  94. "d''",
  95. "e''",
  96. "f''",
  97. "g''",
  98. "a''",
  99. "b''",
  100. "c'''",
  101. "d'''",
  102. "e'''",
  103. "f'''",
  104. "g'''",
  105. "a'''",
  106. "b'''",
  107. "c''''",
  108. "d''''",
  109. "e''''",
  110. "f''''",
  111. "g''''",
  112. "a''''",
  113. "b''''",
  114. ];
  115. const initMusic = (total: number): IMeasure[] => {
  116. return new Array(total).fill(0).map((item, index) => {
  117. return {
  118. measureNumber: index + 1,
  119. barline: "|",
  120. celf: "",
  121. key: "",
  122. repeat: "",
  123. notes: [
  124. {
  125. accidental: "",
  126. clef: "",
  127. meter: "",
  128. content: "z",
  129. noteType: "4",
  130. play: [],
  131. key: "",
  132. speed: "",
  133. dynamics: "",
  134. dCode: "",
  135. tie: "",
  136. tCode: "",
  137. dot: "",
  138. slus: "",
  139. },
  140. ],
  141. };
  142. });
  143. };
  144. function moveNote(note: string, step: number) {
  145. var x = allPitches.indexOf(note);
  146. if (x >= 0) {
  147. const _note = allPitches[x - step];
  148. return _note ? _note : note;
  149. }
  150. return note;
  151. }
  152. export default defineComponent({
  153. name: "Home",
  154. setup() {
  155. const route = useRoute();
  156. const message = useMessage();
  157. const popup = reactive({
  158. instrument: false,
  159. moveKeyShow: false, // 移调弹窗
  160. speedShow: false, // 节拍弹窗
  161. accidentalsShow: false, // 临时升降记号弹窗
  162. clefShow: false, // 谱号弹窗
  163. keyShow: false, // 调号弹窗
  164. meterShow: false, // 拍号弹窗
  165. playShow: false, // 演奏技法弹窗
  166. dynamicsShow: false, // 力度标记弹窗
  167. barShow: false, // 小节弹窗
  168. mearseDeleteShow: false,
  169. staffShow: false, // 五线谱弹窗
  170. settingShow: false, // 设置弹窗
  171. });
  172. const data = reactive({
  173. musicId: "",
  174. musicName: "", // 曲谱名称
  175. subjectId: "", // 声部
  176. speed: "",
  177. music: "",
  178. playState: false, // 播放状态
  179. active: null as unknown as INoteActive,
  180. select: {
  181. state: false,
  182. list: [] as any[],
  183. parmas: null as any,
  184. },
  185. isClickNote: false,
  186. /** 音符类型 */
  187. noteType: "",
  188. selectMeasure: {
  189. start: "",
  190. end: "",
  191. },
  192. slide: ["note", "clef", "key"],
  193. morePlay: false, // 更多演奏技法
  194. addMearseType: "pre" as "pre" | "next" | "finish", // 添加小节类型
  195. addMearseNumber: 1, // 添加小节数量
  196. deleteMearseType: "ing" as "ing" | "finish", // 删除小节类型
  197. });
  198. const noteTypes = ABC_DATA.types.map((item) => item.value).filter(Boolean);
  199. const accidentals = ABC_DATA.accidentals.map((item) => item.value).filter(Boolean);
  200. const clefs = ABC_DATA.clef.map((item) => item.value).filter(Boolean);
  201. const playTypes = ABC_DATA.play.map((item) => item.value).filter(Boolean);
  202. const dynamics = ABC_DATA.dynamics
  203. .map((item) => item.value)
  204. .flat()
  205. .filter(Boolean);
  206. const barTypes = ABC_DATA.bar.map((item) => item.value).filter(Boolean);
  207. console.log("🚀 ~ noteTypes:", noteTypes, accidentals, clefs, playTypes, dynamics);
  208. /** 点击音符 */
  209. const clickListener = (
  210. abcElem: AbcElem,
  211. tuneNumber: number,
  212. classes: string,
  213. analysis: ClickListenerAnalysis,
  214. drag: ClickListenerDrag
  215. ) => {
  216. // console.log("🚀 ~ data.select.state:", data.select.state);
  217. let indexStr: any = abcElem.chord?.find((n) => n.position === "left")?.name || "";
  218. indexStr = indexStr.split(".").map((n: string) => Number(n));
  219. const active = {
  220. ...cloneDeep(abcElem),
  221. measureIndex: indexStr[0],
  222. noteIndex: indexStr[1],
  223. isFirstChecked: true,
  224. };
  225. // 选择范围模式
  226. if (data.select.state) {
  227. data.select.list.push(active);
  228. if (data.select.list.length === 1) {
  229. Snackbar("请先选择结束音符");
  230. }
  231. if (data.select.list.length === 2) {
  232. data.select.list = data.select.list.sort((a, b) => a.startChar - b.startChar);
  233. handleSelectNote();
  234. }
  235. return;
  236. }
  237. data.active = active;
  238. console.log(
  239. "🚀 ~ abcElem:",
  240. abcElem,
  241. data.music.substring(data.active.startChar, data.active.endChar)
  242. );
  243. if (drag) {
  244. // console.log("🚀 ~ drag:", drag);
  245. handleMoveNote("drag", drag.step);
  246. return;
  247. }
  248. if (!abcElem?.midiPitches) return;
  249. ABCJS.synth.playEvent(abcElem.midiPitches, abcElem.midiGraceNotePitches, 1000);
  250. };
  251. const textAreaRef = ref();
  252. const abcData = reactive({
  253. visualObj: null as any,
  254. midiBuffer: null as unknown as ABCJS.MidiBuffer,
  255. abcOptions: {
  256. add_classes: true,
  257. clickListener: clickListener,
  258. responsive: "resize",
  259. dragging: true,
  260. selectTypes: true, // ["note", "clef", "keySignature", "timeSignature", "dynamicDecoration"],
  261. visualTranspose: 0,
  262. wrap: {
  263. minSpacing: 2,
  264. maxSpacing: 10,
  265. preferredMeasuresPerLine: 4,
  266. },
  267. staffwidth: 800,
  268. } as AbcVisualParams,
  269. synthControl: null as unknown as SynthObjectController,
  270. synthOptions: {
  271. program: 0,
  272. // soundFontUrl: "/soundFonts/",
  273. // soundFontUrl: "https://paulrosen.github.io/midi-js-soundfonts/MusyngKite/",
  274. },
  275. abc: {
  276. celf: "K:treble",
  277. minUnit: "L:1/4",
  278. meter: "M:4/4",
  279. speed: "Q:1/4=60",
  280. key: "K:C",
  281. measures: initMusic(30),
  282. } as IAbc,
  283. });
  284. /** 添加音符 */
  285. const handleCreateNote = (measureIndex: number, noteIndex: number, options: INote) => {
  286. // console.log("🚀 ~ noteIndex:", noteIndex);
  287. const measure = abcData.abc.measures[measureIndex];
  288. if (measure) {
  289. measure.notes.splice(noteIndex + 1, 0, options);
  290. }
  291. };
  292. /**
  293. * 分词
  294. * @param str 字符串
  295. * @returns
  296. * @description
  297. */
  298. const tokenize = (str: string) => {
  299. const arr = str.split(/(!.+?!|".+?")/);
  300. let output: string[] = [];
  301. for (let i = 0; i < arr.length; i++) {
  302. const token = arr[i];
  303. if (token.length > 0) {
  304. if (token[0] !== '"' && token[0] !== "!") {
  305. const arr2 = arr[i].split(/([A-Ga-gz][,']*)/);
  306. output = output.concat(arr2);
  307. } else output.push(token);
  308. }
  309. }
  310. return output;
  311. };
  312. const cursorControl = {
  313. // self.onReady = function () {
  314. // var downloadLink = document.querySelector(".download");
  315. // downloadLink.addEventListener("click", download);
  316. // downloadLink.setAttribute("style", "");
  317. // var clickEl = document.querySelector(".click-explanation");
  318. // clickEl.setAttribute("style", "");
  319. // };
  320. onStart: function () {
  321. console.log("开始");
  322. data.playState = true;
  323. var svg = document.querySelector("#paper svg");
  324. var cursor = document.createElementNS("http://www.w3.org/2000/svg", "line");
  325. cursor.setAttribute("class", "ABCJS-cursor");
  326. cursor.setAttributeNS(null, "x1", "0");
  327. cursor.setAttributeNS(null, "y1", "0");
  328. cursor.setAttributeNS(null, "x2", "0");
  329. cursor.setAttributeNS(null, "y2", "0");
  330. svg?.appendChild(cursor);
  331. },
  332. // self.beatSubdivisions = 2;
  333. onBeat: function (beatNumber: any, totalBeats: any, totalTime: any) {},
  334. onEvent: (ev: any) => {
  335. console.log("🚀 ~ ev:", ev);
  336. if (ev.measureStart && ev.left === null) return; // this was the second part of a tie across a measure line. Just ignore it.
  337. var lastSelection = document.querySelectorAll("#paper svg .highlight");
  338. for (var k = 0; k < lastSelection.length; k++) lastSelection[k].classList.remove("highlight");
  339. for (var i = 0; i < ev.elements.length; i++) {
  340. var note = ev.elements[i];
  341. for (var j = 0; j < note.length; j++) {
  342. note[j].classList.add("highlight");
  343. }
  344. }
  345. var cursor = document.querySelector("#paper svg .ABCJS-cursor");
  346. if (cursor) {
  347. cursor.setAttribute("x1", ev.left + ev.width / 2);
  348. cursor.setAttribute("x2", ev.left + ev.width / 2);
  349. cursor.setAttribute("y1", ev.top);
  350. cursor.setAttribute("y2", ev.top + ev.height);
  351. }
  352. },
  353. onFinished: function () {
  354. console.log("finished");
  355. var els = document.querySelectorAll("svg .highlight");
  356. for (var i = 0; i < els.length; i++) {
  357. els[i].classList.remove("highlight");
  358. }
  359. var cursor = document.querySelector("#paper svg .ABCJS-cursor");
  360. if (cursor) {
  361. cursor.setAttribute("x1", "0");
  362. cursor.setAttribute("x2", "0");
  363. cursor.setAttribute("y1", "0");
  364. cursor.setAttribute("y2", "0");
  365. }
  366. },
  367. };
  368. const resetMidi = () => {
  369. abcData.synthControl = new ABCJS.synth.SynthController();
  370. abcData.synthControl.load("#audio", cursorControl, {
  371. displayLoop: true,
  372. displayRestart: true,
  373. displayPlay: true,
  374. displayProgress: true,
  375. });
  376. const midiBuffer = new ABCJS.synth.CreateSynth();
  377. console.log(midiBuffer);
  378. midiBuffer.init({
  379. visualObj: abcData.visualObj,
  380. options: abcData.synthOptions,
  381. });
  382. abcData.synthControl
  383. .setTune(abcData.visualObj, false, {
  384. midiTranspose: abcData.abcOptions.visualTranspose,
  385. ...abcData.synthOptions,
  386. })
  387. .then(function (response) {
  388. // console.log("Audio successfully loaded.");
  389. // console.log("🚀 ~ abcData.synthControl:", abcData.synthControl);
  390. });
  391. };
  392. const togglePlay = (type: "play" | "pause" | "reset") => {
  393. console.log("🚀 ~ abcData.synthControl:", abcData.synthControl);
  394. if (["play", "pause"].includes(type)) {
  395. const playBtn: HTMLElement = document.querySelector(".abcjs-midi-start.abcjs-btn")!;
  396. if (!playBtn) return;
  397. playBtn.click();
  398. data.playState = !data.playState;
  399. } else if (type === "reset") {
  400. const resetBtn: HTMLElement = document.querySelector(".abcjs-midi-reset.abcjs-btn")!;
  401. if (!resetBtn) return;
  402. resetBtn.click();
  403. }
  404. };
  405. const renderSvg = () => {
  406. abcData.visualObj = ABCJS.renderAbc("paper", data.music, abcData.abcOptions)[0];
  407. console.log("🚀 ~ visualObj:", abcData.visualObj);
  408. };
  409. const renderBoxRect = () => {
  410. const svg = document.querySelector("#paper svg");
  411. const padding = 4;
  412. for (let i = 0; i < abcData.visualObj.lines.length; i++) {
  413. const line = abcData.visualObj.lines[i];
  414. for (let j = 0; j < line.staff.length; j++) {
  415. const staff = line.staff[j];
  416. const voices = [...staff.voices].flat();
  417. for (let l = 0; l < voices.length; l++) {
  418. const item = voices[l];
  419. // console.log(item.el_type);
  420. if (["note", "keySignature", "clef", "timeSignature"].includes(item.el_type)) {
  421. const box = item.abselem.elemset?.[0]?.getBBox?.() || null;
  422. // console.log("🚀 ~ box:", item.abselem, box);
  423. if (box) {
  424. const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
  425. rect.setAttributeNS(null, "x", box.x - padding + "");
  426. rect.setAttributeNS(null, "y", box.y - padding + "");
  427. rect.setAttributeNS(null, "width", box.width + padding * 2 + "");
  428. rect.setAttributeNS(null, "height", box.height + padding * 2 + "");
  429. rect.setAttributeNS(null, "fill", "rgba(0,0,0,0)");
  430. rect.setAttributeNS(null, "stroke", "rgba(0,0,0,0)");
  431. rect.setAttributeNS(null, "rx", "2");
  432. rect.classList.add("abcjs-note-hover");
  433. svg?.appendChild(rect);
  434. }
  435. }
  436. }
  437. }
  438. }
  439. // const annotation = document.querySelectorAll("#paper .abcjs-annotation");
  440. // annotation.forEach((n) => {
  441. // n.setAttribute("color", "rgba(0,0,0,0)");
  442. // })
  443. };
  444. /**
  445. * @param isProduct 是否是生成曲谱
  446. */
  447. const handleResetRender = (isProduct = true) => {
  448. return new Promise((resolve) => {
  449. nextTick(() => {
  450. textAreaRef.value.value = data.music = isProduct ? renderMeasures(abcData.abc) : data.music;
  451. renderSvg();
  452. resetMidi();
  453. renderBoxRect();
  454. resolve(1);
  455. });
  456. });
  457. };
  458. // 高亮选中的音符
  459. const rangeHighlight = (startChar: number) => {
  460. // console.log(data.active.endChar, abcData.visualObj.getElementFromChar(data.active.startChar));
  461. const abcElem: AbcElem = abcData.visualObj.getElementFromChar(startChar);
  462. if (abcElem) {
  463. abcData.visualObj.engraver.rangeHighlight(abcElem.startChar, abcElem.endChar);
  464. }
  465. return abcElem;
  466. };
  467. /**
  468. *
  469. * @param key
  470. * @param type note 音符 accidental 临时升降记号
  471. * @returns
  472. */
  473. const handleChange = async (params: { type: string; value: any }) => {
  474. const type = params.type;
  475. const value = params.value;
  476. console.log(params);
  477. if (type === "type") {
  478. // 设置音符类型
  479. data.noteType = value;
  480. return;
  481. }
  482. // 修改音符,或者添加音符
  483. if (type === "note") {
  484. if (data.active) {
  485. if (data.active.el_type !== "note") return;
  486. const activeNote =
  487. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
  488. // console.log(activeNote, data.active.isFirstChecked);
  489. const _values = value.split("-");
  490. console.log("🚀 ~ _value:", _values);
  491. if (data.active.isFirstChecked) {
  492. activeNote.content = _values[0];
  493. activeNote.noteType = data.noteType;
  494. if (_values[1]) {
  495. activeNote.accidental = _values[1] || "";
  496. }
  497. } else {
  498. handleCreateNote(
  499. data.active.measureIndex,
  500. data.active.noteIndex,
  501. createNote({
  502. content: _values[0],
  503. noteType: data.noteType,
  504. accidental: _values[1] || "",
  505. })
  506. );
  507. }
  508. await handleResetRender();
  509. let _abcElem: AbcElem;
  510. if (data.active.isFirstChecked) {
  511. data.active.isFirstChecked = false;
  512. _abcElem = rangeHighlight(data.active.startChar);
  513. } else {
  514. const oldElem: AbcElem = abcData.visualObj.getElementFromChar(data.active.startChar);
  515. const abcElem: AbcElem = abcData.visualObj.getElementFromChar(oldElem.endChar);
  516. if (abcElem) {
  517. let indexStr: any = abcElem.chord?.find((n) => n.position === "left")?.name || "";
  518. indexStr = indexStr.split(".").map((n: string) => Number(n));
  519. data.active = {
  520. ...abcElem,
  521. measureIndex: indexStr[0],
  522. noteIndex: indexStr[1],
  523. isFirstChecked: false,
  524. };
  525. }
  526. _abcElem = rangeHighlight(abcElem.startChar);
  527. }
  528. if (!_abcElem?.midiPitches) return;
  529. ABCJS.synth.playEvent(_abcElem.midiPitches, _abcElem.midiGraceNotePitches, 1000);
  530. } else {
  531. const measureIndex = abcData.abc.measures.length - 1;
  532. const noteIndex = abcData.abc.measures[measureIndex].notes.length - 1;
  533. handleCreateNote(
  534. measureIndex,
  535. noteIndex,
  536. createNote({
  537. content: value,
  538. noteType: data.noteType,
  539. })
  540. );
  541. handleResetRender();
  542. }
  543. }
  544. // 临时升降记号
  545. if (type === "accidentals") {
  546. if (!data.active) {
  547. Snackbar({
  548. content: "请先选择音符",
  549. position: "top",
  550. });
  551. return;
  552. }
  553. const activeNote =
  554. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
  555. if (activeNote.content === "z") {
  556. Snackbar({
  557. content: "休止符无法添加临时升降记号",
  558. });
  559. return;
  560. }
  561. activeNote.accidental = value;
  562. await handleResetRender();
  563. rangeHighlight(data.active.startChar);
  564. }
  565. // 谱号
  566. if (type === "clef") {
  567. if (data.active) {
  568. const activeNote =
  569. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
  570. if (!activeNote) return;
  571. activeNote.clef = `[${value}]`;
  572. await handleResetRender();
  573. } else {
  574. abcData.abc.celf = value;
  575. handleResetRender();
  576. }
  577. }
  578. // 调号
  579. if (type === "key") {
  580. if (data.active) {
  581. const activeNote =
  582. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
  583. if (!activeNote) return;
  584. activeNote.key = `[${value}]`;
  585. await handleResetRender();
  586. } else {
  587. abcData.abc.key = value;
  588. await handleResetRender();
  589. }
  590. }
  591. // 拍号
  592. if (type === "meter") {
  593. if (data.active) {
  594. const activeNote =
  595. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
  596. if (!activeNote) return;
  597. activeNote.meter = `[${value}]`;
  598. await handleResetRender();
  599. } else {
  600. abcData.abc.meter = value;
  601. await handleResetRender();
  602. }
  603. }
  604. // 演奏技法
  605. if (type === "play") {
  606. if (!data.active) {
  607. Snackbar({
  608. content: "请先选择音符",
  609. });
  610. return;
  611. }
  612. const activeNote =
  613. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
  614. if (!activeNote) return;
  615. if (activeNote.play.includes(value)) {
  616. activeNote.play = activeNote.play.filter((item) => item !== value);
  617. } else {
  618. activeNote.play.push(value);
  619. }
  620. await handleResetRender();
  621. rangeHighlight(data.active.startChar);
  622. }
  623. // 力度标记,渐强渐弱
  624. if (type === "dynamics") {
  625. if (Array.isArray(value)) {
  626. // 渐强渐弱
  627. clearSelectNote();
  628. data.select.list = [];
  629. data.select.state = true;
  630. data.select.parmas = params;
  631. Snackbar({
  632. content: "请先选择开始音符",
  633. position: "top",
  634. });
  635. return;
  636. }
  637. if (!data.active) {
  638. Snackbar({
  639. content: "请先选择音符",
  640. });
  641. return;
  642. }
  643. const activeNote =
  644. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
  645. if (!activeNote) return;
  646. if (activeNote.dynamics === value) {
  647. activeNote.dynamics = "";
  648. } else {
  649. activeNote.dynamics = value;
  650. }
  651. await handleResetRender();
  652. rangeHighlight(data.active.startChar);
  653. }
  654. // 连线
  655. if (type === "tie") {
  656. if (Array.isArray(value)) {
  657. // 渐强渐弱
  658. clearSelectNote();
  659. data.select.list = [];
  660. data.select.state = true;
  661. data.select.parmas = params;
  662. Snackbar({
  663. content: "请先选择开始音符",
  664. });
  665. return;
  666. }
  667. if (!data.active) {
  668. Snackbar({
  669. content: "请先选择音符",
  670. });
  671. return;
  672. }
  673. const activeNote =
  674. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
  675. if (!activeNote) return;
  676. if (activeNote.tie === value) {
  677. activeNote.tie = "";
  678. } else {
  679. activeNote.tie = value;
  680. console.log("🚀 ~ activeNote:", activeNote);
  681. }
  682. await handleResetRender();
  683. rangeHighlight(data.active.startChar);
  684. }
  685. // 反复
  686. if (type === "repeat") {
  687. if (!data.active) {
  688. return;
  689. }
  690. const activeMeasure = abcData.abc.measures[data.active.measureIndex] || null;
  691. if (!activeMeasure) return;
  692. if (activeMeasure.repeat === value) {
  693. activeMeasure.repeat = "";
  694. } else {
  695. activeMeasure.repeat = value;
  696. }
  697. await handleResetRender();
  698. rangeHighlight(data.active.startChar + value.length);
  699. }
  700. // 小节线
  701. if (type === "barline") {
  702. if (!data.active) {
  703. return;
  704. }
  705. const activeMeasure = abcData.abc.measures[data.active.measureIndex] || null;
  706. if (!activeMeasure) return;
  707. // 如果是开始重复,就修改前一个小节的barline
  708. if (value === "|:") {
  709. const prevMeasure = abcData.abc.measures[data.active.measureIndex - 1] || null;
  710. if (!prevMeasure) return;
  711. prevMeasure.barline = value;
  712. } else {
  713. activeMeasure.barline = value;
  714. }
  715. await handleResetRender();
  716. }
  717. // 速度
  718. if (type === "speeds") {
  719. if (data.active) {
  720. if (data.active.measureIndex === 0 && data.active.noteIndex === 0) {
  721. abcData.abc.speed = value;
  722. await handleResetRender();
  723. } else {
  724. const activeNote =
  725. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
  726. if (!activeNote) return;
  727. activeNote.speed = `[${value}]`;
  728. await handleResetRender();
  729. }
  730. rangeHighlight(data.active.startChar);
  731. } else {
  732. abcData.abc.speed = value;
  733. await handleResetRender();
  734. }
  735. }
  736. // 附点
  737. if (type === "dot") {
  738. if (!data.active) {
  739. Snackbar({
  740. content: "请先选择音符",
  741. });
  742. return;
  743. }
  744. const activeNote =
  745. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
  746. if (!activeNote) return;
  747. activeNote.dot = activeNote.dot ? "" : value;
  748. await handleResetRender();
  749. rangeHighlight(data.active.startChar);
  750. }
  751. // 3连音
  752. if (type === "slus") {
  753. const activeNote =
  754. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
  755. if (!activeNote) return;
  756. activeNote.slus = activeNote.slus === value ? "" : value;
  757. await handleResetRender();
  758. rangeHighlight(data.active.startChar);
  759. }
  760. };
  761. const handleDeleteNote = () => {
  762. if (!data.active) return;
  763. if (data.active.startChar === 0) return;
  764. data.music =
  765. data.music.substring(0, data.active.startChar) + data.music.substring(data.active.endChar);
  766. handleResetRender();
  767. data.active = null as unknown as INoteActive;
  768. };
  769. const clearSelectNote = () => {
  770. data.active = null as unknown as INoteActive;
  771. const notes = document.querySelectorAll(".abcjs-note_selected");
  772. notes.forEach((item) => {
  773. item.classList.remove("abcjs-note_selected");
  774. item.setAttribute("fill", "currentColor");
  775. });
  776. };
  777. const handleSelectNote = async () => {
  778. const type = data.select.parmas?.type;
  779. const value = data.select.parmas?.value;
  780. const startItem = data.select.list[0];
  781. const endItem = data.select.list[1];
  782. // 力度标记,渐强渐弱
  783. if (type === "dynamics") {
  784. const crescendo = Date.now() + "";
  785. abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].dynamics = value[0];
  786. abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].dCode = crescendo;
  787. abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].dynamics = value[1];
  788. abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].dCode = crescendo;
  789. await handleResetRender();
  790. }
  791. // 连音
  792. if (type === "tie") {
  793. const crescendo = Date.now() + "";
  794. if (abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].tie) {
  795. const tie = abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].tie;
  796. abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].tie = value[0] + tie;
  797. } else {
  798. abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].tie = value[0];
  799. abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].tCode = crescendo;
  800. }
  801. if (abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].tie) {
  802. const tie = abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].tie;
  803. abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].tie = tie + value[1];
  804. } else {
  805. abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].tie = value[1];
  806. abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].tCode = crescendo;
  807. }
  808. await handleResetRender();
  809. }
  810. data.select.state = false;
  811. data.select.list = [];
  812. data.select.parmas = null;
  813. };
  814. /** 移调 */
  815. const handleMoveKey = (step: number) => {
  816. console.log("移调", step);
  817. const oldStep = abcData.abcOptions.visualTranspose || 0;
  818. abcData.abcOptions.visualTranspose = oldStep + step;
  819. popup.moveKeyShow = false;
  820. handleResetRender();
  821. };
  822. /**
  823. * 移动音符
  824. * @param note 音符
  825. * @param step 移动步数
  826. */
  827. const handleMoveNote = async (type: "up" | "donw" | "drag", _step?: number) => {
  828. if (!data.active) return;
  829. const step = _step ? _step : type === "up" ? -1 : 1;
  830. const activeNote =
  831. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
  832. if (!activeNote) return;
  833. activeNote.content = moveNote(activeNote.content, step);
  834. // 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("").
  835. await handleResetRender();
  836. const _abcElem = rangeHighlight(data.active.startChar);
  837. if (!_abcElem?.midiPitches) return;
  838. console.log(_abcElem, abcData.visualObj.millisecondsPerMeasure());
  839. ABCJS.synth.playEvent(_abcElem.midiPitches, _abcElem.midiGraceNotePitches, 1000);
  840. };
  841. const handleKeyUp = (e: KeyboardEvent) => {
  842. if (e.key === "Backspace") handleDeleteNote();
  843. if (!data.active) return false;
  844. if (/^[A-Ga-g]$/.test(e.key)) {
  845. handleChange({ type: "note", value: e.key.toLocaleUpperCase() });
  846. }
  847. if (["ArrowUp", "ArrowDown"].includes(e.key)) {
  848. console.log(e.key);
  849. e.preventDefault();
  850. e.stopPropagation();
  851. handleMoveNote(e.key === "ArrowUp" ? "up" : "donw");
  852. return false;
  853. }
  854. };
  855. /** 重置曲谱 */
  856. const handleCreateSvg = () => {
  857. abcData.abc.measures = initMusic(30);
  858. handleResetRender();
  859. };
  860. const instruments = computed(() => {
  861. return ABCJS.synth.instrumentIndexToName.map((name, index) => ({ label: name, value: index }));
  862. });
  863. const getDetailData = async () => {
  864. const res = await api_musicSheetCreationDetail(route.query.id);
  865. if (res?.code == 200) {
  866. data.musicId = res.data.id || "";
  867. data.musicName = res.data.name || "";
  868. let abc = "" as any;
  869. try {
  870. abc = JSON.parse(res.data.creationData);
  871. } catch (error) {
  872. console.log(error);
  873. }
  874. if (abc) {
  875. abcData.abc.measures = abc;
  876. }
  877. }
  878. };
  879. const handleSaveMusic = async () => {
  880. await api_musicSheetCreationUpdate({
  881. name: data.musicName,
  882. creationConfig: JSON.stringify(abcData.abc.measures),
  883. id: data.musicId,
  884. subjectId: 3,
  885. creationData: data.music,
  886. });
  887. message.success("保存成功");
  888. };
  889. onMounted(async () => {
  890. await getDetailData();
  891. console.log(ABCJS);
  892. await handleResetRender();
  893. console.log(ABCJS.extractMeasures(data.music));
  894. document.addEventListener("keyup", handleKeyUp);
  895. window.onbeforeunload = (e) => {
  896. e.preventDefault();
  897. e.returnValue = '还有没保存的'
  898. };
  899. });
  900. onUnmounted(() => {
  901. document.removeEventListener("keyup", handleKeyUp);
  902. });
  903. const measureComputed = computed(() => {
  904. if (!data.active) return {} as unknown as IMeasure;
  905. const activeMeasure = abcData.abc.measures[data.active.measureIndex] || {};
  906. return activeMeasure;
  907. });
  908. const noteComputed = computed(() => {
  909. if (!data.active) return {} as unknown as INote;
  910. const activeNote =
  911. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || {};
  912. return activeNote;
  913. });
  914. /** 新建曲谱 */
  915. const handleCreateMusic = () => {
  916. showConfirmDialog({
  917. title: "温馨提示",
  918. message: "是否覆盖当前乐谱?",
  919. }).then(() => {
  920. handleCreateSvg();
  921. data.active = null as unknown as INoteActive;
  922. });
  923. };
  924. /** 添加小节 */
  925. const handleAddMearse = () => {
  926. for (let i = 0; i < data.addMearseNumber; i++) {
  927. if (["pre", "next"].includes(data.addMearseType)) {
  928. if (!data.active) {
  929. message.warning("请选择小节");
  930. return;
  931. }
  932. if (data.addMearseType === "pre") {
  933. abcData.abc.measures.splice(data.active.measureIndex, 0, createMeasure());
  934. } else if (data.addMearseType === "next") {
  935. abcData.abc.measures.splice(data.active.measureIndex + 1, 0, createMeasure());
  936. }
  937. } else {
  938. abcData.abc.measures.push(createMeasure());
  939. }
  940. }
  941. popup.barShow = false;
  942. handleResetRender();
  943. };
  944. const handleDeleteMearse = () => {
  945. if (data.deleteMearseType === "ing") {
  946. if (!data.active) {
  947. message.warning("请选择小节");
  948. return;
  949. }
  950. abcData.abc.measures.splice(data.active.measureIndex, 1);
  951. } else if (data.deleteMearseType === "finish") {
  952. abcData.abc.measures.splice(abcData.abc.measures.length - 1, 1);
  953. }
  954. popup.mearseDeleteShow = false;
  955. handleResetRender();
  956. };
  957. const downPng = async () => {
  958. await handleResetRender();
  959. const paper = document.getElementById("paper");
  960. if (!paper) return;
  961. const svg: any = paper.children[0]?.cloneNode(true);
  962. const annotation = svg.querySelectorAll(".abcjs-annotation");
  963. annotation.forEach((n: HTMLElement) => {
  964. n.remove();
  965. });
  966. const svgBox = paper.getBoundingClientRect();
  967. console.log("🚀 ~ svgBox:", svgBox);
  968. svg.setAttribute("width", `${svgBox.width * 3}`);
  969. svg.setAttribute("height", `${svgBox.height * 3}`);
  970. const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
  971. console.log("🚀 ~ svg:", svg);
  972. rect.setAttribute("x", "0");
  973. rect.setAttribute("y", "0");
  974. rect.setAttribute("width", `${svgBox.width * 10}`);
  975. rect.setAttribute("height", `${svgBox.height * 10}`);
  976. rect.setAttribute("fill", "#fff");
  977. svg.prepend(rect);
  978. if (svg) {
  979. const _canvas = svg2canvas(svg.outerHTML);
  980. // document.body.appendChild(_canvas);
  981. let el: any = document.createElement("a");
  982. // 设置 href 为图片经过 base64 编码后的字符串,默认为 png 格式
  983. el.href = _canvas.toDataURL();
  984. el.download = data.musicName + ".png";
  985. // 创建一个点击事件并对 a 标签进行触发
  986. const event = new MouseEvent("click");
  987. el.dispatchEvent(event);
  988. }
  989. };
  990. const downRef = ref();
  991. const downMidi = () => {
  992. const midi = ABCJS.synth.getMidiFile(abcData.visualObj, {
  993. chordsOff: true,
  994. midiOutputType: "link",
  995. fileName: "曲谱",
  996. });
  997. // console.log("🚀 ~ midi:", midi)
  998. downRef.value.innerHTML = midi;
  999. downRef.value.querySelector("a").click();
  1000. };
  1001. const downWav = () => {
  1002. try {
  1003. if (abcData.synthControl) {
  1004. abcData.synthControl.download("曲谱.wav");
  1005. }
  1006. } catch (error) {
  1007. const midiBuffer = new ABCJS.synth.CreateSynth();
  1008. midiBuffer
  1009. .init({
  1010. visualObj: abcData.visualObj,
  1011. options: abcData.synthOptions,
  1012. })
  1013. .then(() => {
  1014. midiBuffer.prime().then(() => {
  1015. // console.log(midiBuffer.download());
  1016. downloadFile(midiBuffer.download(), "曲谱.wav");
  1017. });
  1018. });
  1019. }
  1020. };
  1021. const handleDownFile = (type: IFileBtnType) => {
  1022. if (type === "png") {
  1023. downPng();
  1024. } else if (type === "midi") {
  1025. downMidi();
  1026. } else if (type === "wav") {
  1027. downWav();
  1028. }
  1029. };
  1030. const handleExport = () => {
  1031. const input = document.createElement("input");
  1032. input.type = "file";
  1033. input.accept = ".xml,.musicxml";
  1034. input.onchange = (e: any) => {
  1035. const file = e.target.files[0];
  1036. const reader = new FileReader();
  1037. reader.onload = (e: any) => {
  1038. let abc = e.target.result;
  1039. console.log("🚀 ~ abc:", abc);
  1040. abc = new DOMParser().parseFromString(abc, "text/xml");
  1041. console.log("🚀 ~ abc:", abc);
  1042. abc = (window as any).vertaal(abc, { p: "f", t: 1, u: 0, v: 3, mnum: 0 });
  1043. console.log(abc);
  1044. data.music = abc[0];
  1045. handleResetRender(false);
  1046. };
  1047. reader.readAsText(file);
  1048. };
  1049. input.click();
  1050. };
  1051. return () => (
  1052. <div class={styles.container}>
  1053. <div class={styles.containerTop}>
  1054. <div class={styles.topWrap}>
  1055. <div class={styles.topBtn}>
  1056. <FileBtn
  1057. onSelect={(val: IFileBtnType) => {
  1058. if (val === "newMusic") {
  1059. handleCreateMusic();
  1060. } else if (val === "save") {
  1061. handleSaveMusic();
  1062. } else if (["xml"].includes(val)) {
  1063. handleExport();
  1064. } else if (val === "upload") {
  1065. } else if (["png", "midi", "wav"].includes(val)) {
  1066. handleDownFile(val);
  1067. } else if (val === "print") {
  1068. }
  1069. }}
  1070. />
  1071. <div>文件</div>
  1072. </div>
  1073. <div class={styles.topLine}></div>
  1074. <div class={styles.topBtn} onClick={() => handleChange({ type: "dot", value: ">" })}>
  1075. <div class={[styles.btnImg, noteComputed.value.dot === ">" && styles.btnImgActive]}>
  1076. <img class={styles.topBtnIcon} src={getImage("icon_1.png")} />
  1077. </div>
  1078. <div>附点</div>
  1079. </div>
  1080. <div class={styles.topLine}></div>
  1081. {ABC_DATA.accidentals.map((item) => (
  1082. <div
  1083. class={styles.topBtn}
  1084. onClick={() => handleChange({ type: "accidentals", value: item.value })}
  1085. >
  1086. <div
  1087. class={[
  1088. styles.btnImg,
  1089. noteComputed.value.accidental === item.value && styles.btnImgActive,
  1090. ]}
  1091. >
  1092. <img class={styles.topBtnIcon} src={item.icon} />
  1093. </div>
  1094. <div>{item.name}</div>
  1095. </div>
  1096. ))}
  1097. <div class={styles.topLine}></div>
  1098. {ABC_DATA.tie.map((item) => (
  1099. <div
  1100. class={styles.topBtn}
  1101. onClick={() => handleChange({ type: "tie", value: item.value })}
  1102. >
  1103. <div
  1104. class={[styles.btnImg, noteComputed.value.tie === item.value && styles.btnImgActive]}
  1105. >
  1106. <img class={styles.topBtnIcon} src={item.icon} />
  1107. </div>
  1108. <div>{item.name}</div>
  1109. </div>
  1110. ))}
  1111. <div class={styles.topLine}></div>
  1112. {ABC_DATA.play.slice(0, 4).map((item) => (
  1113. <div
  1114. class={[styles.topBtn]}
  1115. onClick={() => handleChange({ type: "play", value: item.value })}
  1116. >
  1117. <div
  1118. class={[
  1119. styles.btnImg,
  1120. noteComputed.value.play?.includes(item.value) && styles.btnImgActive,
  1121. ]}
  1122. >
  1123. <img class={styles.topBtnIcon} src={item.icon} />
  1124. </div>
  1125. <div>{item.name}</div>
  1126. </div>
  1127. ))}
  1128. <Trigger trigger="click">
  1129. {{
  1130. default: () => (
  1131. <div class={styles.topDownArrow}>
  1132. <img src={getImage("icon_arrow.png")} />
  1133. </div>
  1134. ),
  1135. content: () => (
  1136. <div class={[styles.wrapBox, styles.dropDownWrap]}>
  1137. {ABC_DATA.play.slice(4).map((item) => (
  1138. <div
  1139. class={[styles.topBtn]}
  1140. onClick={() => {
  1141. data.morePlay = false;
  1142. handleChange({ type: "play", value: item.value });
  1143. }}
  1144. >
  1145. <div
  1146. class={[
  1147. styles.btnImg,
  1148. noteComputed.value.play?.includes(item.value) && styles.btnImgActive,
  1149. ]}
  1150. >
  1151. <TheIcon iconClassName={item.icon} />
  1152. </div>
  1153. <div>{item.name}</div>
  1154. </div>
  1155. ))}
  1156. </div>
  1157. ),
  1158. }}
  1159. </Trigger>
  1160. <div class={styles.topLine}></div>
  1161. <NDropdown
  1162. trigger="click"
  1163. options={ABC_DATA.slus as any}
  1164. labelField="name"
  1165. keyField="value"
  1166. onSelect={(val) => {
  1167. console.log(val);
  1168. handleChange({ type: "slus", value: val });
  1169. }}
  1170. >
  1171. <div class={[styles.topBtn]}>
  1172. <div class={styles.btnImg}>
  1173. <img class={styles.topBtnIcon} src={getImage("icon_13.png")} />
  1174. </div>
  1175. <div>连音</div>
  1176. </div>
  1177. </NDropdown>
  1178. <div class={[styles.topBtn, styles.btnDisabled]}>
  1179. <div class={styles.btnImg}>
  1180. <img class={styles.topBtnIcon} src={getImage("icon_14.png")} />
  1181. </div>
  1182. <div>翻转</div>
  1183. </div>
  1184. <div class={styles.topLine}></div>
  1185. <NPopover
  1186. v-model:value={popup.moveKeyShow}
  1187. trigger="click"
  1188. contentStyle={{ width: "320px" }}
  1189. >
  1190. {{
  1191. trigger: () => (
  1192. <div class={styles.topBtn}>
  1193. <div class={styles.btnImg} onClick={() => (popup.instrument = true)}>
  1194. <img class={styles.topBtnIcon} src={getImage("icon_25.png")} />
  1195. </div>
  1196. <div>选择声部</div>
  1197. </div>
  1198. ),
  1199. default: () => (
  1200. <>
  1201. <div class={styles.btnLineTitle}>选择声部</div>
  1202. <NSelect
  1203. filterable
  1204. options={instruments.value}
  1205. v-model:value={abcData.synthOptions.program}
  1206. onChange={() => handleResetRender()}
  1207. ></NSelect>
  1208. </>
  1209. ),
  1210. }}
  1211. </NPopover>
  1212. <NPopover
  1213. v-model:value={popup.moveKeyShow}
  1214. trigger="click"
  1215. contentStyle={{ width: "320px" }}
  1216. >
  1217. {{
  1218. trigger: () => (
  1219. <div class={styles.topBtn}>
  1220. <div class={styles.btnImg}>
  1221. <img class={styles.topBtnIcon} src={getImage("icon_15.png")} />
  1222. </div>
  1223. <div>移调</div>
  1224. </div>
  1225. ),
  1226. default: () => (
  1227. <>
  1228. <div class={styles.btnLineTitle}>移调方式</div>
  1229. <NSpace>
  1230. <NButton secondary onClick={() => handleMoveKey(-1)}>
  1231. <NIcon component={LongArrowAltDown} />
  1232. 向下移调
  1233. </NButton>
  1234. <NButton secondary onClick={() => handleMoveKey(1)}>
  1235. <NIcon component={LongArrowAltUp} />
  1236. 向上移调
  1237. </NButton>
  1238. </NSpace>
  1239. <div class={styles.btnLineTitle}>目标音调</div>
  1240. <NGrid cols={5} yGap={8}>
  1241. {ABC_DATA.key.map((item) => (
  1242. <NGi>
  1243. <div
  1244. class={styles.btnItem}
  1245. onClick={() => handleChange({ type: "key", value: item.value })}
  1246. >
  1247. <div class={[styles.btnItemIcon]}>
  1248. <TheIcon iconClassName={item.icon} />
  1249. </div>
  1250. <div class={styles.btnItemName}>{item.name}</div>
  1251. </div>
  1252. </NGi>
  1253. ))}
  1254. </NGrid>
  1255. </>
  1256. ),
  1257. }}
  1258. </NPopover>
  1259. <NPopover v-model:value={popup.speedShow} trigger="click" placement="bottom">
  1260. {{
  1261. trigger: () => (
  1262. <div class={[styles.topBtn]}>
  1263. <div class={styles.btnImg}>
  1264. <img class={styles.topBtnIcon} src={getImage("icon_16.png")} />
  1265. </div>
  1266. <div>速度调整</div>
  1267. </div>
  1268. ),
  1269. default: () => <TheSpeed onChange={(val) => handleChange(val)} />,
  1270. }}
  1271. </NPopover>
  1272. <NPopover
  1273. v-model:show={popup.staffShow}
  1274. trigger="click"
  1275. placement="bottom"
  1276. contentStyle={{ width: "320px" }}
  1277. >
  1278. {{
  1279. trigger: () => (
  1280. <div class={[styles.topBtn]}>
  1281. <div class={styles.btnImg}>
  1282. <img class={styles.topBtnIcon} src={getImage("icon_17.png")} />
  1283. </div>
  1284. <div>谱面显示</div>
  1285. </div>
  1286. ),
  1287. default: () => (
  1288. <>
  1289. <div class={styles.btnLineTitle}>乐谱大小</div>
  1290. <NSpace>
  1291. <NButton
  1292. type={abcData.abcOptions.staffwidth === 1200 ? "primary" : "default"}
  1293. secondary
  1294. onClick={() => {
  1295. abcData.abcOptions.staffwidth = 1200;
  1296. handleResetRender();
  1297. }}
  1298. >
  1299. </NButton>
  1300. <NButton
  1301. type={abcData.abcOptions.staffwidth === 800 ? "primary" : "default"}
  1302. secondary
  1303. onClick={() => {
  1304. abcData.abcOptions.staffwidth = 800;
  1305. handleResetRender();
  1306. }}
  1307. >
  1308. </NButton>
  1309. <NButton
  1310. type={abcData.abcOptions.staffwidth === 400 ? "primary" : "default"}
  1311. secondary
  1312. onClick={() => {
  1313. abcData.abcOptions.staffwidth = 400;
  1314. handleResetRender();
  1315. }}
  1316. >
  1317. </NButton>
  1318. </NSpace>
  1319. <div class={styles.btnLineTitle}>小节数</div>
  1320. <NSpace vertical>
  1321. <NInputNumber
  1322. min={1}
  1323. v-model:value={(abcData.abcOptions.wrap as any).preferredMeasuresPerLine}
  1324. placeholder="请输入小节数"
  1325. onChange={() => {
  1326. handleResetRender();
  1327. }}
  1328. />
  1329. {/* <NSpace style={{ marginTop: "20px" }} align="center" wrap={false} wrapItem={false}>
  1330. <NButton style={{ width: "48%" }} round onClick={() => (popup.staffShow = false)}>
  1331. 取消
  1332. </NButton>
  1333. <NButton
  1334. style={{ width: "48%" }}
  1335. round
  1336. type="primary"
  1337. >
  1338. 确定
  1339. </NButton>
  1340. </NSpace> */}
  1341. </NSpace>
  1342. </>
  1343. ),
  1344. }}
  1345. </NPopover>
  1346. <NPopover
  1347. v-model:show={popup.barShow}
  1348. trigger="click"
  1349. placement="bottom"
  1350. contentStyle={{ width: "320px" }}
  1351. >
  1352. {{
  1353. trigger: () => (
  1354. <div class={[styles.topBtn]}>
  1355. <div class={styles.btnImg}>
  1356. <img class={styles.topBtnIcon} src={getImage("icon_18.png")} />
  1357. </div>
  1358. <div>添加小节</div>
  1359. </div>
  1360. ),
  1361. default: () => (
  1362. <>
  1363. <div class={styles.btnLineTitle}>添加方式</div>
  1364. <NSpace>
  1365. <NButton
  1366. type={data.addMearseType === "pre" ? "primary" : "default"}
  1367. secondary
  1368. onClick={() => (data.addMearseType = "pre")}
  1369. >
  1370. 当前小节前
  1371. </NButton>
  1372. <NButton
  1373. type={data.addMearseType === "next" ? "primary" : "default"}
  1374. secondary
  1375. onClick={() => (data.addMearseType = "next")}
  1376. >
  1377. 当前小节后
  1378. </NButton>
  1379. <NButton
  1380. type={data.addMearseType === "finish" ? "primary" : "default"}
  1381. secondary
  1382. onClick={() => (data.addMearseType = "finish")}
  1383. >
  1384. 曲谱末尾
  1385. </NButton>
  1386. </NSpace>
  1387. <div class={styles.btnLineTitle}>小节数</div>
  1388. <NSpace vertical>
  1389. <NInputNumber
  1390. min={1}
  1391. v-model:value={data.addMearseNumber}
  1392. placeholder="请输入小节数"
  1393. />
  1394. <NSpace style={{ marginTop: "20px" }} align="center" wrap={false} wrapItem={false}>
  1395. <NButton style={{ width: "48%" }} round onClick={() => (popup.barShow = false)}>
  1396. 取消
  1397. </NButton>
  1398. <NButton
  1399. style={{ width: "48%" }}
  1400. round
  1401. type="primary"
  1402. onClick={() => handleAddMearse()}
  1403. >
  1404. 确定
  1405. </NButton>
  1406. </NSpace>
  1407. </NSpace>
  1408. </>
  1409. ),
  1410. }}
  1411. </NPopover>
  1412. <NPopover v-model:show={popup.mearseDeleteShow} trigger="click" placement="bottom">
  1413. {{
  1414. trigger: () => (
  1415. <div class={[styles.topBtn]}>
  1416. <div class={styles.btnImg}>
  1417. <img class={styles.topBtnIcon} src={getImage("icon_19.png")} />
  1418. </div>
  1419. <div>删除小节</div>
  1420. </div>
  1421. ),
  1422. default: () => (
  1423. <>
  1424. <div class={styles.btnLineTitle}>删除方式</div>
  1425. <NSpace vertical>
  1426. <NSpace>
  1427. <NButton
  1428. type={data.deleteMearseType === "ing" ? "primary" : "default"}
  1429. secondary
  1430. onClick={() => (data.deleteMearseType = "ing")}
  1431. >
  1432. 当前选中小节
  1433. </NButton>
  1434. <NButton
  1435. type={data.deleteMearseType === "finish" ? "primary" : "default"}
  1436. secondary
  1437. onClick={() => (data.deleteMearseType = "finish")}
  1438. >
  1439. 末尾空白小节
  1440. </NButton>
  1441. </NSpace>
  1442. <NSpace style={{ marginTop: "20px" }} align="center" wrap={false} wrapItem={false}>
  1443. <NButton style={{ width: "48%" }} round onClick={() => (popup.barShow = false)}>
  1444. 取消
  1445. </NButton>
  1446. <NButton
  1447. style={{ width: "48%" }}
  1448. round
  1449. type="primary"
  1450. onClick={() => handleDeleteMearse()}
  1451. >
  1452. 确定
  1453. </NButton>
  1454. </NSpace>
  1455. </NSpace>
  1456. </>
  1457. ),
  1458. }}
  1459. </NPopover>
  1460. <div class={styles.topLine}></div>
  1461. <div
  1462. style={{ marginLeft: "auto" }}
  1463. class={styles.topBtn}
  1464. onClick={() => togglePlay("reset")}
  1465. >
  1466. <div class={styles.btnImg}>
  1467. <img class={styles.topBtnIcon} src={getImage("icon_20.png")} />
  1468. </div>
  1469. <div>重播</div>
  1470. </div>
  1471. <div class={styles.topBtn} onClick={() => togglePlay(data.playState ? "pause" : "play")}>
  1472. <div class={styles.btnImg}>
  1473. <img
  1474. style={{ display: data.playState ? "" : "none" }}
  1475. class={styles.topBtnIcon}
  1476. src={getImage("icon_21_1.png")}
  1477. />
  1478. <img
  1479. style={{ display: data.playState ? "none" : "" }}
  1480. class={styles.topBtnIcon}
  1481. src={getImage("icon_21.png")}
  1482. />
  1483. </div>
  1484. <div>{data.playState ? "暂停" : "播放"}</div>
  1485. </div>
  1486. <div class={[styles.topBtn, styles.btnDisabled]}>
  1487. <div class={styles.btnImg}>
  1488. <img class={styles.topBtnIcon} src={getImage("icon_22.png")} />
  1489. </div>
  1490. <div>选段</div>
  1491. </div>
  1492. <div class={[styles.topBtn, styles.btnDisabled]}>
  1493. <div class={styles.btnImg}>
  1494. <img class={styles.topBtnIcon} src={getImage("icon_23.png")} />
  1495. </div>
  1496. <div>节拍器</div>
  1497. </div>
  1498. <div class={[styles.topBtn]} onClick={() => (popup.settingShow = true)}>
  1499. <div class={styles.btnImg}>
  1500. <img class={styles.topBtnIcon} src={getImage("icon_24.png")} />
  1501. </div>
  1502. <div>设置</div>
  1503. </div>
  1504. </div>
  1505. </div>
  1506. <div class={styles.content}>
  1507. <div class={styles.slide}>
  1508. <Collapse v-model={data.slide} elevation={false} divider={false}>
  1509. <CollapseItem title="音符" name="note">
  1510. <div class={styles.wrapBox}>
  1511. {ABC_DATA.types.map((item) => (
  1512. <div
  1513. class={styles.topBtn}
  1514. onClick={() => handleChange({ type: "type", value: item.value })}
  1515. >
  1516. <div class={[styles.btnImg, data.noteType === item.value && styles.btnImgActive]}>
  1517. <TheIcon iconClassName={item.icon} />
  1518. </div>
  1519. <div>{item.name}</div>
  1520. </div>
  1521. ))}
  1522. <div class={styles.topBtn} onClick={() => handleChange({ type: "note", value: "z" })}>
  1523. <div
  1524. class={[styles.btnImg, noteComputed.value.content === "z" && styles.btnImgActive]}
  1525. >
  1526. <img style={{ width: "24px", height: "24px" }} src={getImage("icon_rest.png")} />
  1527. </div>
  1528. <div>休止符</div>
  1529. </div>
  1530. </div>
  1531. </CollapseItem>
  1532. <CollapseItem title="谱号" name="clef">
  1533. <div class={styles.wrapBox}>
  1534. {ABC_DATA.clef.map((item) => (
  1535. <div
  1536. class={styles.topBtn}
  1537. onClick={() => handleChange({ type: "clef", value: item.value })}
  1538. >
  1539. <div class={[styles.btnImg]}>
  1540. <TheIcon iconClassName={item.icon} />
  1541. </div>
  1542. <div>{item.name}</div>
  1543. </div>
  1544. ))}
  1545. </div>
  1546. </CollapseItem>
  1547. <CollapseItem title="调号" name="key">
  1548. <div class={styles.wrapBox}>
  1549. {ABC_DATA.key.map((item) => (
  1550. <div
  1551. class={styles.topBtn}
  1552. onClick={() => handleChange({ type: "key", value: item.value })}
  1553. >
  1554. <div class={[styles.btnImg]}>
  1555. <TheIcon iconClassName={item.icon} />
  1556. </div>
  1557. <div>{item.name}</div>
  1558. </div>
  1559. ))}
  1560. </div>
  1561. </CollapseItem>
  1562. <CollapseItem title="拍号" name="meter">
  1563. <div class={styles.wrapBox}>
  1564. {ABC_DATA.meter.map((item) => (
  1565. <div
  1566. class={styles.topBtn}
  1567. onClick={() => handleChange({ type: "meter", value: item.value })}
  1568. >
  1569. <div class={[styles.btnImg]}>
  1570. <TheIcon iconClassName={item.icon} />
  1571. </div>
  1572. <div>{item.name}</div>
  1573. </div>
  1574. ))}
  1575. </div>
  1576. </CollapseItem>
  1577. <CollapseItem title="力度记号" name="dynamics">
  1578. <div class={styles.wrapBox}>
  1579. {ABC_DATA.dynamics.map((item) => (
  1580. <div
  1581. class={styles.topBtn}
  1582. onClick={() => handleChange({ type: "dynamics", value: item.value })}
  1583. >
  1584. <div
  1585. class={[
  1586. styles.btnImg,
  1587. noteComputed.value.dynamics === item.value && styles.btnImgActive,
  1588. ]}
  1589. >
  1590. <TheIcon iconClassName={item.icon} size={["2em", "2em"]} />
  1591. </div>
  1592. <div>{item.name}</div>
  1593. </div>
  1594. ))}
  1595. </div>
  1596. </CollapseItem>
  1597. <CollapseItem title="反复与跳跃" name="repeat">
  1598. <div class={styles.wrapBox}>
  1599. {ABC_DATA.repeat.map((item) => (
  1600. <div
  1601. class={[styles.topBtn, styles.longTopBtn]}
  1602. onClick={() => handleChange({ type: "repeat", value: item.value })}
  1603. >
  1604. <div
  1605. class={[
  1606. styles.btnImg,
  1607. measureComputed.value.repeat === item.value && styles.btnImgActive,
  1608. ]}
  1609. >
  1610. <TheIcon iconClassName={item.icon} size={["5em", "1em"]} />
  1611. </div>
  1612. <div>{item.name}</div>
  1613. </div>
  1614. ))}
  1615. </div>
  1616. </CollapseItem>
  1617. <CollapseItem title="小节线" name="line">
  1618. <div class={styles.wrapBox}>
  1619. {ABC_DATA.bar.map((item) => (
  1620. <div
  1621. class={styles.topBtn}
  1622. onClick={() => {
  1623. data.morePlay = false;
  1624. handleChange({ type: "barline", value: item.value });
  1625. }}
  1626. >
  1627. <div
  1628. class={[
  1629. styles.btnImg,
  1630. measureComputed.value.barline === item.value && styles.btnImgActive,
  1631. ]}
  1632. >
  1633. <TheIcon iconClassName={item.icon} size={["2em", "2em"]} />
  1634. </div>
  1635. <div>{item.name}</div>
  1636. </div>
  1637. ))}
  1638. </div>
  1639. </CollapseItem>
  1640. </Collapse>
  1641. </div>
  1642. <div class={styles.box}>
  1643. <div class={styles.titleBox}>
  1644. <div style={{ width: "50%", margin: "0 auto" }}>
  1645. <NInput v-model:value={data.musicName} placeholder="乐谱名称" />
  1646. {/* // <Input v-model={data.musicName} variant="outlined" placeholder="乐谱名称" /> */}
  1647. </div>
  1648. </div>
  1649. <div id="paper"></div>
  1650. <Keys show={data.active ? true : false} onClick={(val) => handleChange(val)} />
  1651. {/* <div>
  1652. <button onClick={handleCreateSvg}>重置曲谱</button>
  1653. </div> */}
  1654. <div style={{ display: "none" }}>
  1655. <textarea
  1656. ref={textAreaRef}
  1657. class={styles.value}
  1658. id="abc"
  1659. onChange={() => {
  1660. console.log(textAreaRef.value.value);
  1661. data.music = textAreaRef.value.value;
  1662. handleResetRender();
  1663. }}
  1664. ></textarea>
  1665. </div>
  1666. <div id="audio" style={{ opacity: 0 }}></div>
  1667. <div id="warnings"></div>
  1668. <p class="beat"></p>
  1669. <pre class="clicked-info"></pre>
  1670. <pre class="feedback"></pre>
  1671. <div id="container"></div>
  1672. </div>
  1673. </div>
  1674. <div ref={downRef}></div>
  1675. <TheSetting v-model:show={popup.settingShow} />
  1676. </div>
  1677. );
  1678. },
  1679. });