index.tsx 51 KB

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