index.tsx 70 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314
  1. import { computed, defineComponent, nextTick, onMounted, onUnmounted, reactive, ref } from "vue";
  2. import ABCJS, {
  3. AbcElem,
  4. AbcVisualParams,
  5. ClickListenerAnalysis,
  6. ClickListenerDrag,
  7. NoteTimingEvent,
  8. SynthObjectController,
  9. } from "abcjs";
  10. import "ABCJS/ABCJS-audio.css";
  11. import styles from "./index.module.less";
  12. import { showConfirmDialog, showToast } from "vant";
  13. import Keys from "../component/keys";
  14. import { Collapse, CollapseItem } from "@varlet/ui";
  15. import { IAbc, IMeasure, INote, INoteActive } from "../types";
  16. import {
  17. ABC_DATA,
  18. createMeasure,
  19. createNote,
  20. formateAbc,
  21. getKeyStep,
  22. moveNoteKey,
  23. renderMeasures,
  24. } from "./runtime";
  25. import TheIcon from "/src/components/The-icon";
  26. import { cloneDeep } from "lodash";
  27. import TheSpeed from "./component/the-speed";
  28. import { getImage } from "./images";
  29. import {
  30. NButton,
  31. NDropdown,
  32. NGi,
  33. NGrid,
  34. NIcon,
  35. NInput,
  36. NInputNumber,
  37. NModal,
  38. NPopover,
  39. NPopselect,
  40. NSelect,
  41. NSpace,
  42. NSpin,
  43. useMessage,
  44. } from "naive-ui";
  45. import { LongArrowAltDown, LongArrowAltUp, GripLinesVertical } from "@vicons/fa";
  46. import { svg2canvas } from "/src/utils/svg2canvas";
  47. import { downloadFile } from "/src/utils";
  48. import FileBtn, { IFileBtnType } from "./component/file-btn";
  49. import TheSetting from "./component/the-setting";
  50. import { useRoute } from "vue-router";
  51. import { api_musicSheetCreationDetail, api_musicSheetCreationUpdate } from "../api";
  52. import instrumentsNames from "/src/constant/instrmentsNames.json";
  53. import { ALL_NOTES, ALL_Pitches } from "./noteData";
  54. import { Close } from "@vicons/ionicons5";
  55. import { UseDraggable } from "@vueuse/components";
  56. import { getQuery } from "/src/utils/queryString";
  57. import Metronome, { metronomeData } from "/src/helpers/metronome";
  58. import cleanDeep from "clean-deep";
  59. export const initMusic = (total: number): IMeasure[] => {
  60. return new Array(total).fill(0).map((item, index) => {
  61. return {
  62. measureNumber: index + 1,
  63. barline: "|",
  64. celf: "",
  65. key: "",
  66. repeat: "",
  67. meter: "",
  68. notes: [
  69. {
  70. accidental: "",
  71. clef: "",
  72. meter: "",
  73. content: "z",
  74. noteType: "4",
  75. play: [],
  76. key: "",
  77. speed: "",
  78. dynamics: "",
  79. dCode: "",
  80. tie: "",
  81. tCode: "",
  82. dot: "",
  83. slus: "",
  84. tieline: "",
  85. segno: "",
  86. },
  87. ],
  88. };
  89. });
  90. };
  91. function moveNote(note: string, step: number) {
  92. var x = ALL_Pitches.indexOf(note);
  93. if (x >= 0) {
  94. const _note = ALL_Pitches[x - step];
  95. return _note ? _note : note;
  96. }
  97. return note;
  98. }
  99. export default defineComponent({
  100. name: "Home",
  101. setup() {
  102. const route = useRoute();
  103. const message = useMessage();
  104. const popup = reactive({
  105. instrument: false,
  106. selectSubjectShow: false, // 声部弹窗
  107. moveKeyShow: false, // 移调弹窗
  108. speedShow: false, // 节拍弹窗
  109. accidentalsShow: false, // 临时升降记号弹窗
  110. clefShow: false, // 谱号弹窗
  111. keyShow: false, // 调号弹窗
  112. meterShow: false, // 拍号弹窗
  113. playShow: false, // 演奏技法弹窗
  114. dynamicsShow: false, // 力度标记弹窗
  115. barShow: false, // 小节弹窗
  116. mearseDeleteShow: false,
  117. staffShow: false, // 五线谱弹窗
  118. settingShow: false, // 设置弹窗
  119. selectMearesShow: false, // 选择小节弹窗
  120. });
  121. const data = reactive({
  122. loading: true,
  123. drawCount: 0,
  124. isSave: false,
  125. musicId: "",
  126. musicName: "", // 曲谱名称
  127. creator: "", // 创建者
  128. subjectId: "", // 声部
  129. speed: "",
  130. music: "",
  131. playState: false, // 播放状态
  132. active: null as unknown as INoteActive,
  133. select: {
  134. state: false,
  135. list: [] as any[],
  136. parmas: null as any,
  137. },
  138. isClickNote: false,
  139. /** 音符类型 */
  140. noteType: "",
  141. /** 选择小节范围 */
  142. selectMeasures: {
  143. state: false,
  144. x: 0,
  145. y: 0,
  146. start: 1,
  147. startNote: null as any,
  148. end: 0,
  149. endNote: null as any,
  150. max: 30,
  151. },
  152. slide: ["note", "meter", "dynamics"],
  153. morePlay: false, // 更多演奏技法
  154. addMearseType: "pre" as "pre" | "next" | "finish", // 添加小节类型
  155. addMearseNumber: 1, // 添加小节数量
  156. deleteMearseType: "ing" as "ing" | "finish", // 删除小节类型
  157. loadingAudioSrouce: false, // 加载音频资源
  158. /** 移调类型 */
  159. moveKeyType: "inset" as "inset" | "up" | "down", // 移调类型
  160. });
  161. const noteTypes = ABC_DATA.types.map((item) => item.value).filter(Boolean);
  162. const accidentals = ABC_DATA.accidentals.map((item) => item.value).filter(Boolean);
  163. const clefs = ABC_DATA.clef.map((item) => item.value).filter(Boolean);
  164. const playTypes = ABC_DATA.play.map((item) => item.value).filter(Boolean);
  165. const dynamics = ABC_DATA.dynamics
  166. .map((item) => item.value)
  167. .flat()
  168. .filter(Boolean);
  169. const barTypes = ABC_DATA.bar.map((item) => item.value).filter(Boolean);
  170. console.log("🚀 ~ noteTypes:", noteTypes, accidentals, clefs, playTypes, dynamics);
  171. /** 点击音符 */
  172. const clickListener = (
  173. abcElem: AbcElem,
  174. tuneNumber: number,
  175. classes: string,
  176. analysis: ClickListenerAnalysis,
  177. drag: ClickListenerDrag
  178. ) => {
  179. // console.log("🚀 ~ data.select.state:", data.select.state);
  180. let indexStr: any = abcElem.chord?.find((n) => n.position === "left")?.name || "";
  181. indexStr = indexStr.split(".").map((n: string) => Number(n));
  182. const active = {
  183. ...cloneDeep(abcElem),
  184. measureIndex: indexStr[0],
  185. noteIndex: indexStr[1],
  186. isFirstChecked: true,
  187. };
  188. // 选择范围模式
  189. if (data.select.state) {
  190. data.select.list.push(active);
  191. if (data.select.list.length === 1) {
  192. showToast("请先选择结束音符");
  193. }
  194. if (data.select.list.length === 2) {
  195. console.log(data.select.list);
  196. data.select.list = data.select.list.sort((a, b) => a.startChar - b.startChar);
  197. handleSelectNote();
  198. }
  199. return;
  200. }
  201. data.active = active;
  202. console.log(
  203. "🚀 ~ abcElem:",
  204. abcElem,
  205. data.music.substring(data.active.startChar, data.active.endChar)
  206. );
  207. if (data.active?.el_type === "note") {
  208. const totalTime = (abcData.synthControl as any).visualObj.getTotalTime();
  209. if (totalTime) {
  210. const progress = (data.active as any).currentTrackMilliseconds / 1000 / totalTime;
  211. // console.log("🚀 ~ data.active:", progress);
  212. (abcData.synthControl as any).seek(progress);
  213. }
  214. }
  215. // if (abcElem.el_type === "tempo") {
  216. // abcData.visualObj.engraver.rangeHighlight(abcElem.startChar, abcElem.endChar);
  217. // }
  218. if (drag && drag.step) {
  219. handleChange({ type: "move", value: { action: "drag", step: drag.step } });
  220. return;
  221. }
  222. if (!abcElem?.midiPitches) return;
  223. ABCJS.synth.playEvent(abcElem.midiPitches, abcElem.midiGraceNotePitches, 1000);
  224. };
  225. const textAreaRef = ref();
  226. const abcData = reactive({
  227. visualObj: null as any,
  228. midiBuffer: null as unknown as ABCJS.MidiBuffer,
  229. abcOptions: {
  230. selectionColor: "#0f81ff",
  231. jazzchords: true,
  232. add_classes: true,
  233. clickListener: clickListener,
  234. responsive: "resize",
  235. dragging: true,
  236. selectTypes: ["note"],
  237. visualTranspose: 0,
  238. wrap: {
  239. minSpacing: 0.1,
  240. maxSpacing: 2.7,
  241. preferredMeasuresPerLine: 4,
  242. },
  243. staffwidth: 800,
  244. } as AbcVisualParams,
  245. synthControl: null as unknown as SynthObjectController,
  246. synthOptions: {
  247. program: 0,
  248. // soundFontUrl: "/soundFonts/",
  249. // soundFontUrl: "https://paulrosen.github.io/midi-js-soundfonts/FluidR3_GM/", // 默认 FluidR3_GM
  250. // soundFontUrl: "https://paulrosen.github.io/midi-js-soundfonts/MusyngKite/", // Musyng Kite
  251. },
  252. abc: {
  253. celf: "K:treble",
  254. minUnit: "L:1/4",
  255. meter: "M:4/4",
  256. speed: "Q:1/4=60",
  257. key: "K:C",
  258. visualTranspose: 0,
  259. subjectCode: "acoustic_grand_piano",
  260. measures: initMusic(30),
  261. } as IAbc,
  262. });
  263. /** 添加音符 */
  264. const handleCreateNote = (measureIndex: number, noteIndex: number, options: INote) => {
  265. // console.log("🚀 ~ noteIndex:", noteIndex);
  266. const measure = abcData.abc.measures[measureIndex];
  267. if (measure) {
  268. measure.notes.splice(noteIndex + 1, 0, options);
  269. }
  270. };
  271. const hideCursor = () => {
  272. const cursor = document.querySelector("#paper svg .ABCJS-cursor");
  273. if (cursor) {
  274. cursor.setAttribute("x1", "0");
  275. cursor.setAttribute("x2", "0");
  276. cursor.setAttribute("y1", "0");
  277. cursor.setAttribute("y2", "0");
  278. }
  279. };
  280. const cursorControl = {
  281. onReady: function () {},
  282. onStart: function () {
  283. console.log("开始");
  284. data.playState = true;
  285. var svg = document.querySelector("#paper svg");
  286. var cursor = document.createElementNS("http://www.w3.org/2000/svg", "line");
  287. cursor.setAttribute("class", "ABCJS-cursor");
  288. cursor.setAttributeNS(null, "x1", "0");
  289. cursor.setAttributeNS(null, "y1", "0");
  290. cursor.setAttributeNS(null, "x2", "0");
  291. cursor.setAttributeNS(null, "y2", "0");
  292. svg?.appendChild(cursor);
  293. },
  294. onBeat: function (beatNumber: any, totalBeats: any, totalTime: any) {
  295. if (!data.playState) return;
  296. // console.log("🚀 ~ beatNumber:", (beatNumber / totalBeats) * totalTime);
  297. const time = (beatNumber / totalBeats) * totalTime;
  298. metronomeData.metro.sound(time);
  299. },
  300. onEvent: (ev: any) => {
  301. // console.log("🚀 ~ ev:", ev);
  302. if (!data.playState) return;
  303. if (ev.measureStart && ev.left === null) return; // this was the second part of a tie across a measure line. Just ignore it.
  304. if (popup.selectMearesShow) {
  305. const startTime = data.selectMeasures.startNote?.currentTrackMilliseconds || 0;
  306. const endNote: any = data.selectMeasures.endNote ? data.selectMeasures.endNote : null;
  307. // console.log("🚀 ~ endNote:", ev.milliseconds , endNote.currentTrackMilliseconds)
  308. if (
  309. ev.milliseconds < startTime ||
  310. (endNote && ev.milliseconds > endNote.currentTrackMilliseconds)
  311. ) {
  312. const totalTime = (abcData.synthControl as any).visualObj.getTotalTime();
  313. if (totalTime) {
  314. const progress = startTime / 1000 / totalTime;
  315. nextTick(() => {
  316. (abcData.synthControl as any).seek(progress);
  317. });
  318. }
  319. }
  320. }
  321. var cursor = document.querySelector("#paper svg .ABCJS-cursor");
  322. if (cursor) {
  323. cursor.setAttribute("x1", ev.left + ev.width / 2);
  324. cursor.setAttribute("x2", ev.left + ev.width / 2);
  325. cursor.setAttribute("y1", ev.top);
  326. cursor.setAttribute("y2", ev.top + ev.height);
  327. }
  328. },
  329. onFinished: function () {
  330. console.log("finished");
  331. data.playState = false;
  332. var els = document.querySelectorAll("svg .highlight");
  333. for (var i = 0; i < els.length; i++) {
  334. els[i].classList.remove("highlight");
  335. }
  336. hideCursor();
  337. },
  338. };
  339. const staffNotes = ALL_NOTES();
  340. const loadMiniMp3 = async () => {
  341. data.loadingAudioSrouce = true;
  342. const midiBuffer = new ABCJS.synth.CreateSynth();
  343. const str = `X: 1\nM:4/4\nL:1/4\n${staffNotes}`;
  344. const visualObj = ABCJS.parseOnly(str);
  345. await midiBuffer.init({
  346. visualObj: visualObj[0],
  347. options: { ...abcData.synthOptions },
  348. });
  349. };
  350. const resetMidi = (useActive = false) => {
  351. const midiBuffer = new ABCJS.synth.CreateSynth();
  352. midiBuffer
  353. .init({
  354. visualObj: abcData.visualObj,
  355. options: { ...abcData.synthOptions },
  356. })
  357. .then(() => {
  358. abcData.synthControl
  359. .setTune(abcData.visualObj, useActive, {
  360. midiTranspose: abcData.abc.visualTranspose,
  361. program: abcData.synthOptions.program,
  362. })
  363. .then(function (response) {
  364. data.loadingAudioSrouce = false;
  365. // console.log("Audio successfully loaded.", {...abcData.synthControl});
  366. // console.log("🚀 ~ abcData.synthControl:", abcData.synthControl);
  367. })
  368. .catch((err) => {
  369. console.log(err);
  370. });
  371. });
  372. };
  373. const togglePlay = (type: "play" | "pause" | "reset") => {
  374. if (type === "play") {
  375. abcData.synthControl.play();
  376. data.playState = true;
  377. } else if (type === "pause") {
  378. abcData.synthControl.play();
  379. data.playState = false;
  380. hideCursor();
  381. } else {
  382. abcData.synthControl.restart();
  383. }
  384. // console.log("🚀 ~ abcData.synthControl:", abcData.synthControl.timer.noteTimings);
  385. };
  386. const renderSvg = () => {
  387. abcData.visualObj = ABCJS.renderAbc("paper", data.music, {
  388. ...abcData.abcOptions,
  389. visualTranspose: abcData.abc.visualTranspose,
  390. })[0];
  391. if (data.drawCount < 3) {
  392. console.log("🚀 ~ visualObj:", abcData.visualObj);
  393. }
  394. };
  395. const renderBoxRect = () => {
  396. const svg = document.querySelector("#paper svg");
  397. const padding = 4;
  398. let measureNumber = 0;
  399. for (let i = 0; i < abcData.visualObj.lines.length; i++) {
  400. const line = abcData.visualObj.lines[i];
  401. // console.log("🚀 ~ line:", line);
  402. for (let j = 0; j < line.staff.length; j++) {
  403. const staff = line.staff[j];
  404. const voices = [...staff.voices].flat();
  405. for (let l = 0; l < voices.length; l++) {
  406. const item = voices[l];
  407. if (item.el_type === "bar") {
  408. measureNumber++;
  409. }
  410. // console.log(item.el_type);
  411. if (["note", "keySignature", "clef", "timeSignature"].includes(item.el_type)) {
  412. const box = item.abselem.elemset?.[0]?.getBBox?.() || null;
  413. // console.log("🚀 ~ box:", item.abselem, box);
  414. if (box) {
  415. const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
  416. rect.setAttributeNS(null, "x", box.x - padding + "");
  417. rect.setAttributeNS(null, "y", box.y - padding + "");
  418. rect.setAttributeNS(null, "width", box.width + padding * 2 + "");
  419. rect.setAttributeNS(null, "height", box.height + padding * 2 + "");
  420. rect.setAttributeNS(null, "fill", "rgba(0,0,0,0)");
  421. rect.setAttributeNS(null, "stroke", "rgba(0,0,0,0)");
  422. rect.setAttributeNS(null, "rx", "2");
  423. rect.classList.add("abcjs-note-hover");
  424. svg?.appendChild(rect);
  425. }
  426. }
  427. }
  428. }
  429. }
  430. console.log(measureNumber);
  431. data.selectMeasures.max = measureNumber;
  432. };
  433. /**
  434. * @param isProduct 是否是生成曲谱
  435. */
  436. const handleResetRender = () => {
  437. if (data.drawCount > 0) {
  438. abcData.synthControl.disable(true);
  439. if (data.playState) {
  440. data.playState = false;
  441. }
  442. }
  443. return new Promise((resolve) => {
  444. nextTick(() => {
  445. data.music = renderMeasures(abcData.abc);
  446. renderSvg();
  447. resetMidi(data.drawCount > 0 ? true : false);
  448. renderBoxRect();
  449. try {
  450. productMetronomeData();
  451. } catch (error) {
  452. console.log("🚀 ~ error:", error);
  453. }
  454. resolve(1);
  455. textAreaRef.value && (textAreaRef.value.value = data.music);
  456. data.drawCount++;
  457. });
  458. });
  459. };
  460. /** 生成曲谱节拍器数据 */
  461. const productMetronomeData = () => {
  462. const times = new ABCJS.TimingCallbacks(abcData.visualObj);
  463. const list: any[] = [];
  464. let meter = abcData.abc.meter || "";
  465. // length - 1是为了去除最后一个空的结束事件
  466. for (let i = 0; i < times.noteTimings.length - 1; i++) {
  467. const timeNote = times.noteTimings[i];
  468. const abcNote = getNextNote(timeNote.startChar as number);
  469. let indexStr: any = abcNote.chord?.find((n) => n.position === "left")?.name || "";
  470. indexStr = indexStr.split(".").map((n: string) => Number(n));
  471. if (indexStr.length === 2) {
  472. const measure = abcData.abc.measures[indexStr[0]];
  473. // 如果小节里面有拍号,后面一直延用这个拍号,以此类推, ps: 下个版本
  474. // if (measure.meter){
  475. // meter = measure.meter;
  476. // }
  477. const reg = new RegExp(/M:(\d+)\/\d+/);
  478. const numerator = Number(meter.match(reg)?.[1]);
  479. // console.log("🚀 ~ reg:", meter.match(reg)?.[1], abcData.abc.meter)
  480. // console.log("🚀 ~ measure:", measure)
  481. const note = measure.notes[indexStr[1]];
  482. list.push({
  483. ...note,
  484. timeNote,
  485. abcNote,
  486. measure: {
  487. numerator,
  488. },
  489. });
  490. }
  491. }
  492. console.log("abcData.abc.measures", list);
  493. if (!metronomeData.metro) {
  494. metronomeData.metro = new Metronome();
  495. }
  496. try {
  497. metronomeData.activeIndex = -1;
  498. metronomeData.metro.init(list);
  499. } catch (error) {
  500. console.log("🚀 ~ 生成节拍器数据错误:", error);
  501. }
  502. };
  503. // 高亮选中的音符
  504. const rangeHighlight = (startChar: number) => {
  505. // console.log(data.active.endChar, abcData.visualObj.getElementFromChar(data.active.startChar));
  506. const abcElem: AbcElem = abcData.visualObj.getElementFromChar(startChar);
  507. if (abcElem) {
  508. abcData.visualObj.engraver.rangeHighlight(abcElem.startChar, abcElem.endChar);
  509. }
  510. return abcElem;
  511. };
  512. const useIndexGetNote = (indexStr: string) => {
  513. const index = data.music.indexOf(indexStr);
  514. const note = abcData.visualObj.getElementFromChar(index);
  515. return note;
  516. };
  517. const getMeasureNotes = (measureIndex: number) => {
  518. const measure = abcData.abc.measures[measureIndex];
  519. const notes = [];
  520. for (let k = 0; k < measure.notes.length; k++) {
  521. const indexStr = `${measureIndex}.${k}`;
  522. const note = useIndexGetNote(indexStr);
  523. notes.push(note);
  524. }
  525. return notes;
  526. };
  527. /**
  528. *
  529. * @param key
  530. * @param type note 音符 accidental 临时升降记号
  531. * @returns
  532. */
  533. const handleChange = async (params: { type: string; value: any }) => {
  534. abcData.synthControl.disable(true);
  535. if (data.playState) {
  536. data.playState = false;
  537. }
  538. const type = params.type;
  539. const value = params.value;
  540. const activeNote =
  541. abcData.abc.measures[data.active?.measureIndex]?.notes[data.active?.noteIndex] || null;
  542. if (type === "exit") {
  543. // 退出时先保存 不提示
  544. await handleSaveMusic(false);
  545. // 判断是否在应用中
  546. if (window.matchMedia("(display-mode: standalone)").matches) {
  547. window.onbeforeunload = null;
  548. window.parent.postMessage(
  549. {
  550. api: "notation_exit",
  551. },
  552. "*"
  553. );
  554. } else {
  555. window.close();
  556. }
  557. }
  558. // console.log(params, activeNote);
  559. if (type === "type") {
  560. // 设置音符类型
  561. data.noteType = value;
  562. if (activeNote) {
  563. activeNote.noteType = value;
  564. await handleResetRender();
  565. const abcElem: AbcElem = rangeHighlight(data.active.startChar);
  566. const active: any = abcElem
  567. ? {
  568. ...cloneDeep(abcElem),
  569. measureIndex: data.active.measureIndex,
  570. noteIndex: data.active.noteIndex,
  571. isFirstChecked: true,
  572. }
  573. : null;
  574. data.active = active;
  575. }
  576. return;
  577. }
  578. // 分割
  579. if (type === "segno") {
  580. if (!data.active) {
  581. showToast("请先选择音符");
  582. return;
  583. }
  584. if (!activeNote) return;
  585. activeNote.segno = activeNote.segno ? "" : value;
  586. await handleResetRender();
  587. rangeHighlight(data.active.startChar);
  588. }
  589. // 修改音符,或者添加音符
  590. if (type === "note") {
  591. if (data.active && data.active.el_type == "note") {
  592. const activeNote =
  593. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
  594. const _values = value.split("-");
  595. if (data.active.isFirstChecked) {
  596. activeNote.content = _values[0];
  597. activeNote.noteType = data.noteType;
  598. if (_values[1]) {
  599. activeNote.accidental = _values[1] || "";
  600. }
  601. data.active.isFirstChecked = false;
  602. }
  603. await handleResetRender();
  604. const oldNote = useIndexGetNote(`${data.active.measureIndex}.${data.active.noteIndex}`);
  605. if (oldNote?.abselem?.beam?.elems?.length) {
  606. // 判断是否需要分割beam
  607. const elems: AbcElem[] = oldNote.abselem.beam.elems;
  608. const beatDuration = abcData.visualObj.getBeatLength();
  609. const beamLength = elems.map((n) => n.duration).reduce((a, b) => a + b);
  610. if (beamLength >= beatDuration) {
  611. abcData.abc.measures[data.active.measureIndex].notes[data.active.noteIndex].segno = " ";
  612. await handleResetRender();
  613. }
  614. }
  615. if (oldNote?.midiPitches) {
  616. ABCJS.synth.playEvent(oldNote.midiPitches, oldNote.midiGraceNotePitches, 1000);
  617. }
  618. const nextNote =
  619. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex + 1];
  620. if (nextNote) {
  621. const abcNextElem: AbcElem = useIndexGetNote(
  622. `${data.active.measureIndex}.${data.active.noteIndex + 1}`
  623. );
  624. rangeHighlight(abcNextElem.startChar);
  625. data.active = {
  626. ...abcNextElem,
  627. measureIndex: data.active.measureIndex,
  628. noteIndex: data.active.noteIndex + 1,
  629. isFirstChecked: true,
  630. };
  631. } else {
  632. const notes = getMeasureNotes(data.active.measureIndex);
  633. const duration = notes.map((n) => n.duration).reduce((a, b) => a + b);
  634. if (duration >= 1) {
  635. // 小节内音符总时值过长,自动跳转到下一小节
  636. const nextMeasureNote = abcData.abc.measures[data.active.measureIndex + 1]?.notes[0];
  637. if (nextMeasureNote) {
  638. const abcNextElem: AbcElem = useIndexGetNote(`${data.active.measureIndex + 1}.${0}`);
  639. rangeHighlight(abcNextElem.startChar);
  640. data.active = {
  641. ...abcNextElem,
  642. measureIndex: data.active.measureIndex + 1,
  643. noteIndex: 0,
  644. isFirstChecked: true,
  645. };
  646. } else {
  647. // 到最后一个小节的最后一个音符了
  648. rangeHighlight(data.active.startChar);
  649. data.active.isFirstChecked = true;
  650. }
  651. } else {
  652. handleCreateNote(
  653. data.active.measureIndex,
  654. data.active.noteIndex,
  655. createNote({
  656. content: "z",
  657. noteType: data.noteType,
  658. })
  659. );
  660. await handleResetRender();
  661. const newNote = useIndexGetNote(
  662. `${data.active.measureIndex}.${data.active.noteIndex + 1}`
  663. );
  664. rangeHighlight(newNote.startChar);
  665. data.active = {
  666. ...newNote,
  667. measureIndex: data.active.measureIndex,
  668. noteIndex: data.active.noteIndex + 1,
  669. isFirstChecked: true,
  670. };
  671. }
  672. }
  673. }
  674. }
  675. // 临时升降记号
  676. if (type === "accidentals") {
  677. if (!data.active) {
  678. message.warning("请先选择音符");
  679. return;
  680. }
  681. if (activeNote.content === "z") {
  682. message.warning("休止符无法添加临时升降记号");
  683. return;
  684. }
  685. activeNote.accidental = activeNote.accidental == value ? "" : value;
  686. await handleResetRender();
  687. const abcElem: AbcElem = rangeHighlight(data.active.startChar);
  688. const active: any = abcElem
  689. ? {
  690. ...cloneDeep(abcElem),
  691. measureIndex: data.active.measureIndex,
  692. noteIndex: data.active.noteIndex,
  693. isFirstChecked: true,
  694. }
  695. : null;
  696. data.active = active;
  697. }
  698. // 谱号
  699. if (type === "clef") {
  700. if (data.active) {
  701. if (!activeNote) return;
  702. activeNote.clef = `[${value}]`;
  703. await handleResetRender();
  704. } else {
  705. abcData.abc.celf = value;
  706. handleResetRender();
  707. }
  708. }
  709. // 调号
  710. if (type === "key") {
  711. if (data.active) {
  712. if (!activeNote) return;
  713. activeNote.key = `[${value}]`;
  714. await handleResetRender();
  715. } else {
  716. abcData.abc.key = value;
  717. await handleResetRender();
  718. }
  719. }
  720. // 拍号
  721. if (type === "meter") {
  722. if (data.active && data.active.measureIndex !== 0) {
  723. if (!activeNote) return;
  724. const measure = abcData.abc.measures[data.active.measureIndex];
  725. measure.meter = `[${value}]`;
  726. await handleResetRender();
  727. const oldNote = useIndexGetNote(`${data.active.measureIndex}.${data.active.noteIndex}`);
  728. rangeHighlight(oldNote.startChar);
  729. } else {
  730. abcData.abc.meter = value;
  731. await handleResetRender();
  732. }
  733. }
  734. // 演奏技法
  735. if (type === "play") {
  736. if (!data.active) {
  737. message.warning("请先选择音符");
  738. return;
  739. }
  740. if (!activeNote) return;
  741. if (activeNote.play.includes(value)) {
  742. activeNote.play = activeNote.play.filter((item) => item !== value);
  743. } else {
  744. activeNote.play.push(value);
  745. }
  746. await handleResetRender();
  747. rangeHighlight(data.active.startChar);
  748. }
  749. // 力度标记,渐强渐弱
  750. if (type === "dynamics") {
  751. if (!data.active) {
  752. message.info("请先选择音符");
  753. return;
  754. }
  755. if (!activeNote) return;
  756. if (Array.isArray(value)) {
  757. if (activeNote?.dynamics) {
  758. activeNote.dynamics = "";
  759. for (let i = 0; i < abcData.abc.measures.length; i++) {
  760. const measure = abcData.abc.measures[i];
  761. for (let j = 0; j < measure.notes.length; j++) {
  762. const note = measure.notes[j];
  763. if (note.dCode === activeNote.dCode) {
  764. note.dynamics = "";
  765. }
  766. }
  767. }
  768. await handleResetRender();
  769. } else {
  770. data.select.list = [cloneDeep(data.active)];
  771. data.select.state = true;
  772. data.select.parmas = params;
  773. message.info("请选择结束音符");
  774. }
  775. return;
  776. }
  777. if (activeNote.dynamics === value) {
  778. activeNote.dynamics = "";
  779. } else {
  780. activeNote.dynamics = value;
  781. }
  782. await handleResetRender();
  783. rangeHighlight(data.active.startChar);
  784. }
  785. // 延音
  786. if (type === "tie") {
  787. if (!data.active) {
  788. message.info("请先选择音符");
  789. return;
  790. }
  791. if (!activeNote) return;
  792. if (Array.isArray(value)) {
  793. if (activeNote?.tie) {
  794. activeNote.tie = "";
  795. for (let i = 0; i < abcData.abc.measures.length; i++) {
  796. const measure = abcData.abc.measures[i];
  797. for (let j = 0; j < measure.notes.length; j++) {
  798. const note = measure.notes[j];
  799. if (note.tCode === activeNote.tCode) {
  800. note.tie = "";
  801. }
  802. }
  803. }
  804. await handleResetRender();
  805. return;
  806. } else {
  807. data.select.list = [cloneDeep(data.active)];
  808. data.select.state = true;
  809. data.select.parmas = params;
  810. message.info("请选择结束音符");
  811. return;
  812. }
  813. }
  814. const abcElem: any = getNextNote(data.active.endChar);
  815. if (activeNote.tieline) {
  816. activeNote.tieline = "";
  817. } else {
  818. if (data.active.averagepitch != abcElem.averagepitch) {
  819. message.warning("必须同音高才能添加延音线");
  820. return;
  821. }
  822. activeNote.tieline = value;
  823. }
  824. await handleResetRender();
  825. rangeHighlight(data.active.startChar);
  826. }
  827. // 反复
  828. if (type === "repeat") {
  829. if (!data.active) {
  830. return;
  831. }
  832. const activeMeasure = abcData.abc.measures[data.active.measureIndex] || null;
  833. if (!activeMeasure) return;
  834. if (activeMeasure.repeat === value) {
  835. activeMeasure.repeat = "";
  836. } else {
  837. activeMeasure.repeat = value;
  838. }
  839. await handleResetRender();
  840. rangeHighlight(data.active.startChar + value.length);
  841. }
  842. // 小节线
  843. if (type === "barline") {
  844. if (!data.active) {
  845. return;
  846. }
  847. const activeMeasure = abcData.abc.measures[data.active.measureIndex] || null;
  848. if (!activeMeasure) return;
  849. // 如果是开始重复,就修改前一个小节的barline
  850. if (value === "|:") {
  851. const prevMeasure = abcData.abc.measures[data.active.measureIndex - 1] || null;
  852. if (!prevMeasure) return;
  853. prevMeasure.barline = value;
  854. } else {
  855. activeMeasure.barline = value;
  856. }
  857. await handleResetRender();
  858. }
  859. // 速度
  860. if (type === "speeds") {
  861. if (data.active) {
  862. if (data.active.measureIndex === 0 && data.active.noteIndex === 0) {
  863. abcData.abc.speed = value;
  864. await handleResetRender();
  865. } else {
  866. const activeNote =
  867. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
  868. if (!activeNote) return;
  869. activeNote.speed = `[${value}]`;
  870. await handleResetRender();
  871. }
  872. rangeHighlight(data.active.startChar);
  873. } else {
  874. abcData.abc.speed = value;
  875. await handleResetRender();
  876. }
  877. }
  878. // 附点
  879. if (type === "dot") {
  880. if (!data.active) {
  881. showToast("请先选择音符");
  882. return;
  883. }
  884. if (!activeNote) return;
  885. activeNote.dot = activeNote.dot ? "" : value;
  886. // activeNote.dot = activeNote.dot ? "" : NOTE_DOT[activeNote.noteType];
  887. await handleResetRender();
  888. rangeHighlight(data.active.startChar);
  889. }
  890. // 3连音
  891. if (type === "slus") {
  892. const activeNote =
  893. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || null;
  894. if (!activeNote) return;
  895. activeNote.slus = activeNote.slus === value ? "" : value;
  896. await handleResetRender();
  897. rangeHighlight(data.active.startChar);
  898. }
  899. // 移动音符
  900. if (type === "move") {
  901. const step = value._step ? value._step : value.action === "up" ? -1 : 1;
  902. if (!activeNote) return;
  903. activeNote.content = moveNote(activeNote.content, step);
  904. // 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("").
  905. await handleResetRender();
  906. const _abcElem = rangeHighlight(data.active.startChar);
  907. if (!_abcElem?.midiPitches) return;
  908. ABCJS.synth.playEvent(_abcElem.midiPitches, _abcElem.midiGraceNotePitches, 1000);
  909. }
  910. // 删除音符
  911. if (type === "delete") {
  912. if (!data.active) return;
  913. if (data.active.startChar === 0) return;
  914. abcData.abc.measures[data.active.measureIndex].notes.splice(data.active.noteIndex, 1);
  915. if (abcData.abc.measures[data.active.measureIndex].notes.length === 0) {
  916. abcData.abc.measures.splice(data.active.measureIndex, 1);
  917. }
  918. handleResetRender();
  919. data.active = null as unknown as INoteActive;
  920. }
  921. };
  922. const getNextNote = (index: number): AbcElem => {
  923. const abcElem = abcData.visualObj.getElementFromChar(index);
  924. if (abcElem.el_type === "note") {
  925. return abcElem;
  926. } else {
  927. return getNextNote(abcElem.endChar);
  928. }
  929. };
  930. const handleDeleteNote = () => {
  931. if (!data.active) return;
  932. if (data.active.startChar === 0) return;
  933. abcData.abc.measures[data.active.measureIndex].notes.splice(data.active.noteIndex, 1);
  934. if (abcData.abc.measures[data.active.measureIndex].notes.length === 0) {
  935. abcData.abc.measures.splice(data.active.measureIndex, 1);
  936. }
  937. handleResetRender();
  938. data.active = null as unknown as INoteActive;
  939. };
  940. const clearSelectNote = () => {
  941. data.active = null as unknown as INoteActive;
  942. const notes = document.querySelectorAll(".abcjs-note_selected");
  943. notes.forEach((item) => {
  944. item.classList.remove("abcjs-note_selected");
  945. item.setAttribute("fill", "currentColor");
  946. });
  947. };
  948. const handleSelectNote = async () => {
  949. const type = data.select.parmas?.type;
  950. const value = data.select.parmas?.value;
  951. const startItem = data.select.list[0];
  952. const endItem = data.select.list[1];
  953. // 力度标记,渐强渐弱
  954. if (type === "dynamics") {
  955. if (
  956. abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].dynamics ||
  957. abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].dynamics
  958. ) {
  959. message.warning("已经添加了力度标记");
  960. } else {
  961. const crescendo = Date.now() + "";
  962. abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].dynamics = value[0];
  963. abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].dCode = crescendo;
  964. abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].dynamics = value[1];
  965. abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].dCode = crescendo;
  966. await handleResetRender();
  967. }
  968. }
  969. // 连音
  970. if (type === "tie") {
  971. const crescendo = Date.now() + "";
  972. if (abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].tie) {
  973. const tie = abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].tie;
  974. abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].tie = value[0] + tie;
  975. } else {
  976. abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].tie = value[0];
  977. abcData.abc.measures[startItem.measureIndex].notes[startItem.noteIndex].tCode = crescendo;
  978. }
  979. if (abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].tie) {
  980. const tie = abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].tie;
  981. abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].tie = tie + value[1];
  982. } else {
  983. abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].tie = value[1];
  984. abcData.abc.measures[endItem.measureIndex].notes[endItem.noteIndex].tCode = crescendo;
  985. }
  986. await handleResetRender();
  987. }
  988. data.select.state = false;
  989. data.select.list = [];
  990. data.select.parmas = null;
  991. clearSelectNote();
  992. message.destroyAll();
  993. };
  994. /** 移调 */
  995. const handleMoveKey = async (item: (typeof ABC_DATA.key)[0]) => {
  996. // const moveData = getKeyStep(item.value, abcData.abc.key, data.moveKeyType);
  997. // console.log("🚀 ~ item:", abcData.abc.key, "=>", item.value, moveData);
  998. // 将所有的音符移调
  999. // for (let i = 0; i < abcData.abc.measures.length; i++) {
  1000. // const measure = abcData.abc.measures[i];
  1001. // for (let j = 0; j < measure.notes.length; j++) {
  1002. // const note = measure.notes[j];
  1003. // if (note.content == "z") continue;
  1004. // const content = moveNoteKey(note.content, moveData);
  1005. // const _a = content.substring(0, 1);
  1006. // if (_a === "^" || _a === "_") {
  1007. // note.content = content.substring(1);
  1008. // } else {
  1009. // note.content = content;
  1010. // }
  1011. // console.log("🚀 ~ note.content:", note.content);
  1012. // }
  1013. // }
  1014. // // console.log(abcData.abc.visualTranspose, item.step, item.value)
  1015. // abcData.abc.key = item.value;
  1016. // const step =
  1017. // data.moveKeyType === "up"
  1018. // ? item.step > 0
  1019. // ? item.step
  1020. // : item.step + 12
  1021. // : data.moveKeyType === "down"
  1022. // ? item.step < 0
  1023. // ? item.step
  1024. // : item.step + 12
  1025. // : item.step;
  1026. abcData.abc.visualTranspose = item.step;
  1027. popup.moveKeyShow = false;
  1028. if (data.playState) {
  1029. abcData.synthControl.disable(true);
  1030. data.playState = false;
  1031. }
  1032. await handleResetRender();
  1033. };
  1034. const handleKeyUp = (e: KeyboardEvent) => {
  1035. if (!data.active) return false;
  1036. console.log(e.key);
  1037. if (e.key === "Backspace") {
  1038. handleChange({ type: "delete", value: "" });
  1039. }
  1040. if (/^[A-Ga-g]$/.test(e.key)) {
  1041. handleChange({ type: "note", value: e.key.toLocaleUpperCase() });
  1042. }
  1043. if (["ArrowUp", "ArrowDown"].includes(e.key)) {
  1044. e.preventDefault();
  1045. e.stopPropagation();
  1046. handleChange({ type: "move", value: { action: e.key === "ArrowUp" ? "up" : "donw" } });
  1047. return false;
  1048. }
  1049. };
  1050. /** 重置曲谱 */
  1051. const handleCreateSvg = () => {
  1052. abcData.abc.measures = initMusic(30);
  1053. handleResetRender();
  1054. };
  1055. const instruments = computed(() => {
  1056. return ABCJS.synth.instrumentIndexToName.map((name, index) => ({
  1057. label: instrumentsNames[name as keyof typeof instrumentsNames],
  1058. value: index,
  1059. }));
  1060. });
  1061. const getDetailData = async () => {
  1062. data.loading = true;
  1063. const query: any = getQuery();
  1064. const res = await api_musicSheetCreationDetail(query.id);
  1065. if (res?.code == 200) {
  1066. data.musicId = res.data.id || "";
  1067. data.musicName = res.data.name || "";
  1068. data.creator = res.data.creator || "";
  1069. let abc = "" as any;
  1070. try {
  1071. abc = JSON.parse(res.data.creationData);
  1072. } catch (error) {
  1073. console.log(error);
  1074. }
  1075. if (abc) {
  1076. console.log("🚀 ~ abc:", abc);
  1077. abcData.abc.celf = abc.celf || "K:treble";
  1078. abcData.abc.key = abc.key.value || "K:C";
  1079. abcData.abc.meter = abc.meter.value || "M:4/4";
  1080. abcData.abc.speed = abc.speed || "Q:1/4=60";
  1081. abcData.abc.visualTranspose = abc.visualTranspose || 0;
  1082. abcData.abc.subjectCode = abc.subjectCode || "acoustic_grand_piano";
  1083. const _instruments = ABCJS.synth.instrumentIndexToName.indexOf(abcData.abc.subjectCode as any);
  1084. abcData.synthOptions.program = _instruments > -1 ? _instruments : 0;
  1085. abcData.abc.measures = abc.measures || initMusic(30);
  1086. // console.log("🚀 ~ abcData.abc:", abcData.abc);
  1087. }
  1088. }
  1089. data.loading = false;
  1090. };
  1091. const handleSaveMusic = async (tips = true) => {
  1092. await api_musicSheetCreationUpdate({
  1093. name: data.musicName,
  1094. creator: data.creator,
  1095. creationConfig: renderMeasures(abcData.abc, { hiddenIndex: true }),
  1096. creationData: JSON.stringify(cleanDeep(abcData.abc)),
  1097. id: data.musicId,
  1098. subjectId: 3,
  1099. });
  1100. if (tips) {
  1101. message.success("保存成功");
  1102. }
  1103. data.isSave = true;
  1104. };
  1105. onMounted(async () => {
  1106. await getDetailData();
  1107. loadMiniMp3();
  1108. if (ABCJS.synth.supportsAudio()) {
  1109. abcData.synthControl = new ABCJS.synth.SynthController();
  1110. // console.log("🚀 ~ abcData.synthControl:", abcData.synthControl)
  1111. abcData.synthControl.load("#audio", cursorControl, {
  1112. displayLoop: true,
  1113. displayRestart: true,
  1114. displayPlay: true,
  1115. displayProgress: true,
  1116. });
  1117. }
  1118. console.log(ABCJS);
  1119. await handleResetRender();
  1120. document.addEventListener("keyup", handleKeyUp);
  1121. window.onbeforeunload = (e) => {
  1122. if (!data.isSave) {
  1123. e.preventDefault();
  1124. e.returnValue = "还有没保存的";
  1125. }
  1126. };
  1127. abcData.synthControl.restart();
  1128. // formateAbc(abcData.visualObj);
  1129. const selectMearesBtn = document.querySelector("#selectMearesBtn");
  1130. if (selectMearesBtn) {
  1131. const rect = selectMearesBtn.getBoundingClientRect();
  1132. data.selectMeasures.x = document.body.clientWidth - 320;
  1133. data.selectMeasures.y = rect.top + 70;
  1134. data.selectMeasures.state = true;
  1135. }
  1136. });
  1137. onUnmounted(() => {
  1138. document.removeEventListener("keyup", handleKeyUp);
  1139. });
  1140. const measureComputed = computed(() => {
  1141. if (!data.active) return {} as unknown as IMeasure;
  1142. const activeMeasure = abcData.abc.measures[data.active.measureIndex] || {};
  1143. return activeMeasure;
  1144. });
  1145. const noteComputed = computed(() => {
  1146. if (!data.active) return {} as unknown as INote;
  1147. const activeNote =
  1148. abcData.abc.measures[data.active.measureIndex]?.notes[data.active.noteIndex] || {};
  1149. return activeNote;
  1150. });
  1151. /** 新建曲谱 */
  1152. const handleCreateMusic = () => {
  1153. showConfirmDialog({
  1154. title: "温馨提示",
  1155. message: "是否覆盖当前乐谱?",
  1156. }).then(() => {
  1157. handleCreateSvg();
  1158. data.active = null as unknown as INoteActive;
  1159. });
  1160. };
  1161. /** 添加小节 */
  1162. const handleAddMearse = () => {
  1163. for (let i = 0; i < data.addMearseNumber; i++) {
  1164. if (["pre", "next"].includes(data.addMearseType)) {
  1165. if (!data.active) {
  1166. message.warning("请选择小节");
  1167. return;
  1168. }
  1169. if (data.addMearseType === "pre") {
  1170. abcData.abc.measures.splice(data.active.measureIndex, 0, createMeasure());
  1171. } else if (data.addMearseType === "next") {
  1172. abcData.abc.measures.splice(data.active.measureIndex + 1, 0, createMeasure());
  1173. }
  1174. } else {
  1175. abcData.abc.measures.push(createMeasure());
  1176. }
  1177. }
  1178. popup.barShow = false;
  1179. handleResetRender();
  1180. };
  1181. const handleDeleteMearse = () => {
  1182. if (data.deleteMearseType === "ing") {
  1183. if (!data.active) {
  1184. message.warning("请选择小节");
  1185. return;
  1186. }
  1187. abcData.abc.measures.splice(data.active.measureIndex, 1);
  1188. } else if (data.deleteMearseType === "finish") {
  1189. let len = abcData.abc.measures.length;
  1190. for (let i = len; i > 0; i--) {
  1191. if (
  1192. abcData.abc.measures[i - 1].notes.length === 1 &&
  1193. abcData.abc.measures[i - 1].notes[0].content === "z"
  1194. ) {
  1195. if (abcData.abc.measures.length === 1) {
  1196. break;
  1197. }
  1198. abcData.abc.measures.splice(i - 1, 1);
  1199. } else {
  1200. break;
  1201. }
  1202. }
  1203. }
  1204. popup.mearseDeleteShow = false;
  1205. handleResetRender();
  1206. };
  1207. const downPng = async () => {
  1208. abcData.abc.title = `T:${data.musicName}`;
  1209. abcData.abc.creator = `R:${data.creator}`;
  1210. const paper = document.getElementById("exportPng");
  1211. if (!paper) return;
  1212. const abc = renderMeasures(abcData.abc, { hiddenIndex: true, showTitle: true, showCreator: true });
  1213. ABCJS.renderAbc(paper, abc, abcData.abcOptions);
  1214. const svg: any = paper.children[0]?.cloneNode(true);
  1215. const svgBox = paper.getBoundingClientRect();
  1216. svg.setAttribute("width", `${svgBox.width * 3}`);
  1217. svg.setAttribute("height", `${svgBox.height * 3}`);
  1218. const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
  1219. rect.setAttribute("x", "0");
  1220. rect.setAttribute("y", "0");
  1221. rect.setAttribute("width", `${svgBox.width * 10}`);
  1222. rect.setAttribute("height", `${svgBox.height * 10}`);
  1223. rect.setAttribute("fill", "#fff");
  1224. svg.prepend(rect);
  1225. if (svg) {
  1226. const _canvas = svg2canvas(svg.outerHTML);
  1227. // document.body.appendChild(_canvas);
  1228. let el: any = document.createElement("a");
  1229. // 设置 href 为图片经过 base64 编码后的字符串,默认为 png 格式
  1230. el.href = _canvas.toDataURL();
  1231. el.download = data.musicName + ".png";
  1232. // 创建一个点击事件并对 a 标签进行触发
  1233. const event = new MouseEvent("click");
  1234. el.dispatchEvent(event);
  1235. }
  1236. };
  1237. const downRef = ref();
  1238. const downMidi = () => {
  1239. const midi = ABCJS.synth.getMidiFile(abcData.visualObj, {
  1240. chordsOff: true,
  1241. midiOutputType: "link",
  1242. fileName: "曲谱",
  1243. });
  1244. // console.log("🚀 ~ midi:", midi)
  1245. downRef.value.innerHTML = midi;
  1246. downRef.value.querySelector("a").click();
  1247. };
  1248. const downWav = () => {
  1249. try {
  1250. if (abcData.synthControl) {
  1251. abcData.synthControl.download("曲谱.wav");
  1252. }
  1253. } catch (error) {
  1254. const midiBuffer = new ABCJS.synth.CreateSynth();
  1255. midiBuffer
  1256. .init({
  1257. visualObj: abcData.visualObj,
  1258. options: abcData.synthOptions,
  1259. })
  1260. .then(() => {
  1261. midiBuffer.prime().then(() => {
  1262. // console.log(midiBuffer.download());
  1263. downloadFile(midiBuffer.download(), "曲谱.wav");
  1264. });
  1265. });
  1266. }
  1267. };
  1268. const handleDownFile = (type: IFileBtnType) => {
  1269. if (type === "png") {
  1270. downPng();
  1271. } else if (type === "midi") {
  1272. downMidi();
  1273. } else if (type === "wav") {
  1274. downWav();
  1275. }
  1276. };
  1277. const handleExport = () => {
  1278. const input = document.createElement("input");
  1279. input.type = "file";
  1280. input.accept = ".xml,.musicxml";
  1281. input.onchange = (e: any) => {
  1282. const file = e.target.files[0];
  1283. const reader = new FileReader();
  1284. reader.onload = (e: any) => {
  1285. let abc = e.target.result;
  1286. console.log("🚀 ~ abc:", abc);
  1287. abc = new DOMParser().parseFromString(abc, "text/xml");
  1288. console.log("🚀 ~ abc:", abc);
  1289. abc = (window as any).vertaal(abc, { p: "f", t: 1, u: 0, v: 3, mnum: 0 });
  1290. console.log(abc);
  1291. // data.music = abc[0];
  1292. // handleResetRender(false);
  1293. };
  1294. reader.readAsText(file);
  1295. };
  1296. input.click();
  1297. };
  1298. /** 设置选段小节 */
  1299. const handleSetSelectMeares = (index: number | null, type: "start" | "end") => {
  1300. console.log("🚀 ~ index:", index);
  1301. if (type === "start") {
  1302. const note = index ? useIndexGetNote(`${index - 1}.0`) : null;
  1303. // console.log("🚀 ~ note:", note);
  1304. data.selectMeasures.start = index ? index - 1 : 0;
  1305. data.selectMeasures.startNote = note;
  1306. if (
  1307. data.selectMeasures.start &&
  1308. data.selectMeasures.end &&
  1309. data.selectMeasures.end < data.selectMeasures.start
  1310. ) {
  1311. data.selectMeasures.end = 0;
  1312. data.selectMeasures.endNote = null;
  1313. }
  1314. } else {
  1315. const note = index
  1316. ? useIndexGetNote(`${index - 1}.${abcData.abc.measures[index - 1]?.notes.length - 1}`)
  1317. : null;
  1318. // console.log("🚀 ~ note:", note);
  1319. data.selectMeasures.end = index ? index - 1 : 0;
  1320. data.selectMeasures.endNote = note;
  1321. if (
  1322. data.selectMeasures.start &&
  1323. data.selectMeasures.end &&
  1324. data.selectMeasures.start > data.selectMeasures.end
  1325. ) {
  1326. // console.log(data.selectMeasures.start, data.selectMeasures.end);
  1327. data.selectMeasures.start = 0;
  1328. data.selectMeasures.startNote = null;
  1329. }
  1330. }
  1331. };
  1332. return () => (
  1333. <>
  1334. <div class={styles.container}>
  1335. <div class={styles.containerTop} onKeyup={(e: Event) => e.stopPropagation()}>
  1336. <div class={styles.topWrap}>
  1337. <div class={styles.topBtn} onClick={() => handleChange({ type: "exit", value: "exit" })}>
  1338. <div class={[styles.btnImg]}>
  1339. <img class={styles.topBtnIcon} src={getImage("icon_-1.png")} />
  1340. </div>
  1341. <div>退出</div>
  1342. </div>
  1343. <div class={styles.topBtn}>
  1344. <FileBtn
  1345. onSelect={(val: IFileBtnType) => {
  1346. if (val === "newMusic") {
  1347. handleCreateMusic();
  1348. } else if (val === "save") {
  1349. handleSaveMusic();
  1350. } else if (["xml"].includes(val)) {
  1351. handleExport();
  1352. } else if (val === "upload") {
  1353. } else if (["png", "midi", "wav"].includes(val)) {
  1354. handleDownFile(val);
  1355. } else if (val === "print") {
  1356. }
  1357. // else if (val === "exit") {
  1358. // }
  1359. }}
  1360. />
  1361. <div>文件</div>
  1362. </div>
  1363. <div class={styles.topLine}></div>
  1364. <div class={styles.topBtn} onClick={() => handleChange({ type: "dot", value: ">" })}>
  1365. <div class={[styles.btnImg, noteComputed.value.dot === ">" && styles.btnImgActive]}>
  1366. <img class={styles.topBtnIcon} src={getImage("icon_1.png")} />
  1367. </div>
  1368. <div>附点</div>
  1369. </div>
  1370. <div class={styles.topLine}></div>
  1371. {ABC_DATA.accidentals.map((item) => (
  1372. <div
  1373. class={styles.topBtn}
  1374. onClick={() => handleChange({ type: "accidentals", value: item.value })}
  1375. >
  1376. <div
  1377. class={[
  1378. styles.btnImg,
  1379. noteComputed.value.accidental === item.value && styles.btnImgActive,
  1380. ]}
  1381. >
  1382. <img class={styles.topBtnIcon} src={item.icon} />
  1383. </div>
  1384. <div class={styles.btnName}>{item.name}</div>
  1385. </div>
  1386. ))}
  1387. <div class={styles.topLine}></div>
  1388. <div
  1389. class={styles.topBtn}
  1390. onClick={() => handleChange({ type: "tie", value: ABC_DATA.tie[0].value })}
  1391. >
  1392. <div
  1393. class={[
  1394. styles.btnImg,
  1395. noteComputed.value.tieline === ABC_DATA.tie[0].value && styles.btnImgActive,
  1396. ]}
  1397. >
  1398. <img class={styles.topBtnIcon} src={ABC_DATA.tie[0].icon} />
  1399. </div>
  1400. <div>{ABC_DATA.tie[0].name}</div>
  1401. </div>
  1402. <div
  1403. class={styles.topBtn}
  1404. onClick={() => handleChange({ type: "tie", value: ABC_DATA.tie[1].value })}
  1405. >
  1406. <div
  1407. class={[
  1408. styles.btnImg,
  1409. ABC_DATA.tie[1].value.includes(noteComputed.value.tie) && styles.btnImgActive,
  1410. ]}
  1411. >
  1412. <img class={styles.topBtnIcon} src={ABC_DATA.tie[1].icon} />
  1413. </div>
  1414. <div>{ABC_DATA.tie[1].name}</div>
  1415. </div>
  1416. <div class={styles.topLine}></div>
  1417. {ABC_DATA.play.slice(0, 4).map((item) => (
  1418. <div
  1419. class={[styles.topBtn]}
  1420. onClick={() => handleChange({ type: "play", value: item.value })}
  1421. >
  1422. <div
  1423. class={[
  1424. styles.btnImg,
  1425. noteComputed.value.play?.includes(item.value) && styles.btnImgActive,
  1426. ]}
  1427. >
  1428. <img class={styles.topBtnIcon} src={item.icon} />
  1429. </div>
  1430. <div>{item.name}</div>
  1431. </div>
  1432. ))}
  1433. <NPopover
  1434. class={styles.popupWrap}
  1435. showArrow={false}
  1436. trigger="click"
  1437. contentStyle={{ width: "400px" }}
  1438. >
  1439. {{
  1440. trigger: () => (
  1441. <div class={styles.topDownArrow}>
  1442. <img src={getImage("icon_arrow.png")} />
  1443. </div>
  1444. ),
  1445. default: () => (
  1446. <NGrid cols={4} yGap={8}>
  1447. {ABC_DATA.play.slice(4).map((item) => (
  1448. <NGi>
  1449. <div
  1450. class={[
  1451. styles.btnItem,
  1452. noteComputed.value.play?.includes(item.value) && styles.btnItemActive,
  1453. ]}
  1454. onClick={() => {
  1455. data.morePlay = false;
  1456. handleChange({ type: "play", value: item.value });
  1457. }}
  1458. >
  1459. <div class={styles.btnItemIcon}>
  1460. <TheIcon iconClassName={item.icon} />
  1461. </div>
  1462. <div>{item.name}</div>
  1463. </div>
  1464. </NGi>
  1465. ))}
  1466. </NGrid>
  1467. ),
  1468. }}
  1469. </NPopover>
  1470. <div class={styles.topLine}></div>
  1471. <NDropdown
  1472. trigger="click"
  1473. options={ABC_DATA.slus as any}
  1474. labelField="name"
  1475. keyField="value"
  1476. onSelect={(val) => {
  1477. console.log(val);
  1478. handleChange({ type: "slus", value: val });
  1479. }}
  1480. >
  1481. <div class={[styles.topBtn]}>
  1482. <div class={styles.btnImg}>
  1483. <img class={styles.topBtnIcon} src={getImage("icon_13.png")} />
  1484. </div>
  1485. <div>连音</div>
  1486. </div>
  1487. </NDropdown>
  1488. <div class={[styles.topBtn, styles.btnDisabled]}>
  1489. <div class={styles.btnImg}>
  1490. <img class={styles.topBtnIcon} src={getImage("icon_14.png")} />
  1491. </div>
  1492. <div>翻转</div>
  1493. </div>
  1494. <div class={styles.topLine}></div>
  1495. <NPopover
  1496. class={styles.popupWrap}
  1497. showArrow={false}
  1498. v-model:show={popup.selectSubjectShow}
  1499. trigger="click"
  1500. contentStyle={{ width: "320px" }}
  1501. >
  1502. {{
  1503. trigger: () => (
  1504. <div class={styles.topBtn}>
  1505. <div class={styles.btnImg} onClick={() => (popup.instrument = true)}>
  1506. <img class={styles.topBtnIcon} src={getImage("icon_25.png")} />
  1507. </div>
  1508. <div>选择声部</div>
  1509. </div>
  1510. ),
  1511. default: () => (
  1512. <>
  1513. <div class={styles.btnLineTitle}>选择声部</div>
  1514. <NSelect
  1515. filterable
  1516. options={instruments.value}
  1517. v-model:value={abcData.synthOptions.program}
  1518. onUpdate:value={async () => {
  1519. abcData.synthControl.disable(true);
  1520. data.playState = false;
  1521. await loadMiniMp3();
  1522. resetMidi(true);
  1523. popup.selectSubjectShow = false;
  1524. }}
  1525. ></NSelect>
  1526. </>
  1527. ),
  1528. }}
  1529. </NPopover>
  1530. <NPopover
  1531. class={styles.popupWrap}
  1532. showArrow={false}
  1533. v-model:show={popup.moveKeyShow}
  1534. trigger="click"
  1535. contentStyle={{ width: "320px" }}
  1536. >
  1537. {{
  1538. trigger: () => (
  1539. <div class={styles.topBtn}>
  1540. <div class={styles.btnImg}>
  1541. <img class={styles.topBtnIcon} src={getImage("icon_15.png")} />
  1542. </div>
  1543. <div>移调</div>
  1544. </div>
  1545. ),
  1546. default: () => (
  1547. <>
  1548. <div class={styles.btnLineTitle}>移调方式</div>
  1549. <NSpace>
  1550. <NButton
  1551. secondary
  1552. type={data.moveKeyType === "inset" ? "primary" : "default"}
  1553. onClick={() => (data.moveKeyType = "inset")}
  1554. >
  1555. <NIcon component={GripLinesVertical} />
  1556. 最靠近
  1557. </NButton>
  1558. <NButton
  1559. secondary
  1560. type={data.moveKeyType === "down" ? "primary" : "default"}
  1561. onClick={() => (data.moveKeyType = "down")}
  1562. >
  1563. <NIcon component={LongArrowAltDown} />
  1564. 向下移调
  1565. </NButton>
  1566. <NButton
  1567. secondary
  1568. type={data.moveKeyType === "up" ? "primary" : "default"}
  1569. onClick={() => (data.moveKeyType = "up")}
  1570. >
  1571. <NIcon component={LongArrowAltUp} />
  1572. 向上移调
  1573. </NButton>
  1574. </NSpace>
  1575. <div class={styles.btnLineTitle}>目标音调</div>
  1576. <NGrid cols={5} yGap={8}>
  1577. {ABC_DATA.key
  1578. .sort((a, b) => b.step - a.step)
  1579. .map((item) => (
  1580. <NGi>
  1581. <div
  1582. class={[
  1583. styles.btnItem,
  1584. abcData.abc.key === item.value && styles.btnItemActive,
  1585. ]}
  1586. onClick={() => handleMoveKey(item)}
  1587. >
  1588. <div class={[styles.btnItemIcon]}>
  1589. <TheIcon iconClassName={item.icon} />
  1590. </div>
  1591. <div class={styles.btnItemName}>{item.name}</div>
  1592. </div>
  1593. </NGi>
  1594. ))}
  1595. </NGrid>
  1596. </>
  1597. ),
  1598. }}
  1599. </NPopover>
  1600. <NPopover
  1601. class={styles.popupWrap}
  1602. showArrow={false}
  1603. v-model:value={popup.speedShow}
  1604. trigger="click"
  1605. placement="bottom"
  1606. displayDirective="show"
  1607. >
  1608. {{
  1609. trigger: () => (
  1610. <div class={[styles.topBtn]}>
  1611. <div class={styles.btnImg}>
  1612. <img class={styles.topBtnIcon} src={getImage("icon_16.png")} />
  1613. </div>
  1614. <div>速度调整</div>
  1615. </div>
  1616. ),
  1617. default: () => <TheSpeed onChange={(val) => handleChange(val)} />,
  1618. }}
  1619. </NPopover>
  1620. <NPopover
  1621. class={styles.popupWrap}
  1622. showArrow={false}
  1623. v-model:show={popup.staffShow}
  1624. trigger="click"
  1625. placement="bottom"
  1626. contentStyle={{ width: "320px" }}
  1627. >
  1628. {{
  1629. trigger: () => (
  1630. <div class={[styles.topBtn]}>
  1631. <div class={styles.btnImg}>
  1632. <img class={styles.topBtnIcon} src={getImage("icon_17.png")} />
  1633. </div>
  1634. <div>谱面显示</div>
  1635. </div>
  1636. ),
  1637. default: () => (
  1638. <>
  1639. <div class={styles.btnLineTitle}>乐谱大小</div>
  1640. <NSpace>
  1641. <NButton
  1642. type={abcData.abcOptions.staffwidth === 1200 ? "primary" : "default"}
  1643. secondary
  1644. onClick={() => {
  1645. abcData.abcOptions.staffwidth = 1200;
  1646. handleResetRender();
  1647. }}
  1648. >
  1649. </NButton>
  1650. <NButton
  1651. type={abcData.abcOptions.staffwidth === 800 ? "primary" : "default"}
  1652. secondary
  1653. onClick={() => {
  1654. abcData.abcOptions.staffwidth = 800;
  1655. handleResetRender();
  1656. }}
  1657. >
  1658. </NButton>
  1659. <NButton
  1660. type={abcData.abcOptions.staffwidth === 400 ? "primary" : "default"}
  1661. secondary
  1662. onClick={() => {
  1663. abcData.abcOptions.staffwidth = 400;
  1664. handleResetRender();
  1665. }}
  1666. >
  1667. </NButton>
  1668. </NSpace>
  1669. <div class={styles.btnLineTitle}>小节数</div>
  1670. <NSpace vertical>
  1671. <NInputNumber
  1672. min={1}
  1673. v-model:value={(abcData.abcOptions.wrap as any).preferredMeasuresPerLine}
  1674. placeholder="请输入小节数"
  1675. onUpdate:value={() => {
  1676. handleResetRender();
  1677. }}
  1678. />
  1679. {/* <NSpace style={{ marginTop: "20px" }} align="center" wrap={false} wrapItem={false}>
  1680. <NButton style={{ width: "48%" }} round onClick={() => (popup.staffShow = false)}>
  1681. 取消
  1682. </NButton>
  1683. <NButton
  1684. style={{ width: "48%" }}
  1685. round
  1686. type="primary"
  1687. >
  1688. 确定
  1689. </NButton>
  1690. </NSpace> */}
  1691. </NSpace>
  1692. </>
  1693. ),
  1694. }}
  1695. </NPopover>
  1696. <NPopover
  1697. class={styles.popupWrap}
  1698. showArrow={false}
  1699. v-model:show={popup.barShow}
  1700. trigger="click"
  1701. placement="bottom"
  1702. contentStyle={{ width: "320px" }}
  1703. >
  1704. {{
  1705. trigger: () => (
  1706. <div class={[styles.topBtn]}>
  1707. <div class={styles.btnImg}>
  1708. <img class={styles.topBtnIcon} src={getImage("icon_18.png")} />
  1709. </div>
  1710. <div>添加小节</div>
  1711. </div>
  1712. ),
  1713. default: () => (
  1714. <>
  1715. <div class={styles.btnLineTitle}>添加方式</div>
  1716. <NSpace>
  1717. <NButton
  1718. type={data.addMearseType === "pre" ? "primary" : "default"}
  1719. secondary
  1720. onClick={() => (data.addMearseType = "pre")}
  1721. >
  1722. 当前小节前
  1723. </NButton>
  1724. <NButton
  1725. type={data.addMearseType === "next" ? "primary" : "default"}
  1726. secondary
  1727. onClick={() => (data.addMearseType = "next")}
  1728. >
  1729. 当前小节后
  1730. </NButton>
  1731. <NButton
  1732. type={data.addMearseType === "finish" ? "primary" : "default"}
  1733. secondary
  1734. onClick={() => (data.addMearseType = "finish")}
  1735. >
  1736. 曲谱末尾
  1737. </NButton>
  1738. </NSpace>
  1739. <div class={styles.btnLineTitle}>小节数</div>
  1740. <NSpace vertical>
  1741. <NInputNumber
  1742. min={1}
  1743. v-model:value={data.addMearseNumber}
  1744. placeholder="请输入小节数"
  1745. />
  1746. <NSpace
  1747. style={{ marginTop: "20px" }}
  1748. align="center"
  1749. wrap={false}
  1750. wrapItem={false}
  1751. >
  1752. <NButton
  1753. style={{ width: "48%" }}
  1754. round
  1755. onClick={() => (popup.barShow = false)}
  1756. >
  1757. 取消
  1758. </NButton>
  1759. <NButton
  1760. style={{ width: "48%" }}
  1761. round
  1762. type="primary"
  1763. onClick={() => handleAddMearse()}
  1764. >
  1765. 确定
  1766. </NButton>
  1767. </NSpace>
  1768. </NSpace>
  1769. </>
  1770. ),
  1771. }}
  1772. </NPopover>
  1773. <NPopover
  1774. class={styles.popupWrap}
  1775. v-model:show={popup.mearseDeleteShow}
  1776. trigger="click"
  1777. placement="bottom"
  1778. >
  1779. {{
  1780. trigger: () => (
  1781. <div class={[styles.topBtn]}>
  1782. <div class={styles.btnImg}>
  1783. <img class={styles.topBtnIcon} src={getImage("icon_19.png")} />
  1784. </div>
  1785. <div>删除小节</div>
  1786. </div>
  1787. ),
  1788. default: () => (
  1789. <>
  1790. <div class={styles.btnLineTitle}>删除方式</div>
  1791. <NSpace vertical>
  1792. <NSpace>
  1793. <NButton
  1794. type={data.deleteMearseType === "ing" ? "primary" : "default"}
  1795. secondary
  1796. onClick={() => (data.deleteMearseType = "ing")}
  1797. >
  1798. 当前选中小节
  1799. </NButton>
  1800. <NButton
  1801. type={data.deleteMearseType === "finish" ? "primary" : "default"}
  1802. secondary
  1803. onClick={() => (data.deleteMearseType = "finish")}
  1804. >
  1805. 末尾空白小节
  1806. </NButton>
  1807. </NSpace>
  1808. <NSpace
  1809. style={{ marginTop: "20px" }}
  1810. align="center"
  1811. wrap={false}
  1812. wrapItem={false}
  1813. >
  1814. <NButton
  1815. style={{ width: "48%" }}
  1816. round
  1817. onClick={() => (popup.mearseDeleteShow = false)}
  1818. >
  1819. 取消
  1820. </NButton>
  1821. <NButton
  1822. style={{ width: "48%" }}
  1823. round
  1824. type="primary"
  1825. onClick={() => handleDeleteMearse()}
  1826. >
  1827. 确定
  1828. </NButton>
  1829. </NSpace>
  1830. </NSpace>
  1831. </>
  1832. ),
  1833. }}
  1834. </NPopover>
  1835. <div class={styles.topLine}></div>
  1836. <div
  1837. style={{ marginLeft: "auto" }}
  1838. class={styles.topBtn}
  1839. onClick={() => togglePlay("reset")}
  1840. >
  1841. <div class={styles.btnImg}>
  1842. <img class={styles.topBtnIcon} src={getImage("icon_20.png")} />
  1843. </div>
  1844. <div>重播</div>
  1845. </div>
  1846. <div class={styles.topBtn}>
  1847. <NSpin show={data.loadingAudioSrouce} size="small">
  1848. <div
  1849. class={styles.btnImg}
  1850. onClick={() => togglePlay(data.playState ? "pause" : "play")}
  1851. >
  1852. <img
  1853. style={{ display: data.playState ? "" : "none" }}
  1854. class={styles.topBtnIcon}
  1855. src={getImage("icon_21_1.png")}
  1856. />
  1857. <img
  1858. style={{ display: data.playState ? "none" : "" }}
  1859. class={styles.topBtnIcon}
  1860. src={getImage("icon_21.png")}
  1861. />
  1862. </div>
  1863. </NSpin>
  1864. <div>{data.playState ? "暂停" : "播放"}</div>
  1865. </div>
  1866. <div
  1867. id="selectMearesBtn"
  1868. class={[styles.topBtn]}
  1869. onClick={() => (popup.selectMearesShow = !popup.selectMearesShow)}
  1870. >
  1871. <div class={[styles.btnImg, popup.selectMearesShow && styles.btnImgActive]}>
  1872. <img class={styles.topBtnIcon} src={getImage("icon_22.png")} />
  1873. </div>
  1874. <div>选段</div>
  1875. </div>
  1876. <div
  1877. class={[styles.topBtn]}
  1878. onClick={() => {
  1879. metronomeData.disable = !metronomeData.disable;
  1880. metronomeData.metro?.initPlayer();
  1881. }}
  1882. >
  1883. <div class={[styles.btnImg, !metronomeData.disable && styles.btnImgActive]}>
  1884. <img class={styles.topBtnIcon} src={getImage("icon_23.png")} />
  1885. </div>
  1886. <div>节拍器</div>
  1887. </div>
  1888. <div class={[styles.topBtn]} onClick={() => (popup.settingShow = true)}>
  1889. <div class={styles.btnImg}>
  1890. <img class={styles.topBtnIcon} src={getImage("icon_24.png")} />
  1891. </div>
  1892. <div>设置</div>
  1893. </div>
  1894. </div>
  1895. </div>
  1896. <div class={styles.content}>
  1897. <div class={styles.slide}>
  1898. <Collapse v-model={data.slide} elevation={false} divider={false}>
  1899. <CollapseItem title="音符时值" name="note">
  1900. <div class={styles.wrapBox}>
  1901. {ABC_DATA.types.map((item) => (
  1902. <div
  1903. class={styles.topBtn}
  1904. onClick={() => handleChange({ type: "type", value: item.value })}
  1905. >
  1906. <div
  1907. class={[styles.btnImg, data.noteType === item.value && styles.btnImgActive]}
  1908. >
  1909. <TheIcon iconClassName={item.icon} />
  1910. </div>
  1911. <div>{item.name}</div>
  1912. </div>
  1913. ))}
  1914. <div
  1915. class={styles.topBtn}
  1916. onClick={() => handleChange({ type: "note", value: "z" })}
  1917. >
  1918. <div
  1919. class={[
  1920. styles.btnImg,
  1921. noteComputed.value.content === "z" && styles.btnImgActive,
  1922. ]}
  1923. >
  1924. <img style={{ width: "20px", height: "20px" }} src={getImage("icon_rest.png")} />
  1925. </div>
  1926. <div>休止符</div>
  1927. </div>
  1928. <div
  1929. class={styles.topBtn}
  1930. onClick={() => handleChange({ type: "segno", value: " " })}
  1931. >
  1932. <div
  1933. class={[styles.btnImg, noteComputed.value.segno === " " && styles.btnImgActive]}
  1934. >
  1935. {/* <img style={{ width: "20px", height: "20px" }} src={getImage("icon_rest.png")} /> */}
  1936. </div>
  1937. <div>分割</div>
  1938. </div>
  1939. </div>
  1940. </CollapseItem>
  1941. <CollapseItem title="拍号" name="meter">
  1942. <div class={styles.wrapBox}>
  1943. {ABC_DATA.meter.map((item) => (
  1944. <div
  1945. class={styles.topBtn}
  1946. onClick={() => handleChange({ type: "meter", value: item.value })}
  1947. >
  1948. <div class={[styles.btnImg]}>
  1949. <TheIcon iconClassName={item.icon} />
  1950. </div>
  1951. <div>{item.name}</div>
  1952. </div>
  1953. ))}
  1954. </div>
  1955. </CollapseItem>
  1956. <CollapseItem title="力度记号" name="dynamics">
  1957. <div class={styles.wrapBox}>
  1958. {ABC_DATA.dynamics.slice(0, 8).map((item) => (
  1959. <div
  1960. class={styles.topBtn}
  1961. onClick={() => handleChange({ type: "dynamics", value: item.value })}
  1962. >
  1963. <div
  1964. class={[
  1965. styles.btnImg,
  1966. noteComputed.value.dynamics === item.value && styles.btnImgActive,
  1967. ]}
  1968. >
  1969. <TheIcon iconClassName={item.icon} size={["2em", "2em"]} />
  1970. </div>
  1971. <div>{item.name}</div>
  1972. </div>
  1973. ))}
  1974. <div
  1975. class={styles.topBtn}
  1976. onClick={() =>
  1977. handleChange({ type: "dynamics", value: ABC_DATA.dynamics[8].value })
  1978. }
  1979. >
  1980. <div
  1981. class={[
  1982. styles.btnImg,
  1983. ABC_DATA.dynamics[8].value.includes(noteComputed.value.dynamics) &&
  1984. styles.btnImgActive,
  1985. ]}
  1986. >
  1987. <TheIcon iconClassName={ABC_DATA.dynamics[8].icon} size={["2em", "2em"]} />
  1988. </div>
  1989. <div>{ABC_DATA.dynamics[8].name}</div>
  1990. </div>
  1991. <div
  1992. class={styles.topBtn}
  1993. onClick={() =>
  1994. handleChange({ type: "dynamics", value: ABC_DATA.dynamics[9].value })
  1995. }
  1996. >
  1997. <div
  1998. class={[
  1999. styles.btnImg,
  2000. ABC_DATA.dynamics[9].value.includes(noteComputed.value.dynamics) &&
  2001. styles.btnImgActive,
  2002. ]}
  2003. >
  2004. <TheIcon iconClassName={ABC_DATA.dynamics[9].icon} size={["2em", "2em"]} />
  2005. </div>
  2006. <div>{ABC_DATA.dynamics[9].name}</div>
  2007. </div>
  2008. </div>
  2009. </CollapseItem>
  2010. <CollapseItem title="反复与跳跃" name="repeat">
  2011. <div class={styles.wrapBox}>
  2012. {ABC_DATA.repeat.map((item) => (
  2013. <div
  2014. class={[styles.topBtn, styles.longTopBtn]}
  2015. onClick={() => handleChange({ type: "repeat", value: item.value })}
  2016. >
  2017. <div
  2018. class={[
  2019. styles.btnImg,
  2020. measureComputed.value.repeat === item.value && styles.btnImgActive,
  2021. ]}
  2022. >
  2023. <TheIcon iconClassName={item.icon} size={["5em", "1em"]} />
  2024. </div>
  2025. <div>{item.name}</div>
  2026. </div>
  2027. ))}
  2028. </div>
  2029. </CollapseItem>
  2030. <CollapseItem title="小节线" name="line">
  2031. <div class={styles.wrapBox}>
  2032. {ABC_DATA.bar.map((item) => (
  2033. <div
  2034. class={styles.topBtn}
  2035. onClick={() => {
  2036. data.morePlay = false;
  2037. handleChange({ type: "barline", value: item.value });
  2038. }}
  2039. >
  2040. <div
  2041. class={[
  2042. styles.btnImg,
  2043. measureComputed.value.barline === item.value && styles.btnImgActive,
  2044. ]}
  2045. >
  2046. <TheIcon iconClassName={item.icon} size={["2em", "2em"]} />
  2047. </div>
  2048. <div>{item.name}</div>
  2049. </div>
  2050. ))}
  2051. </div>
  2052. </CollapseItem>
  2053. <CollapseItem title="谱号" name="clef">
  2054. <div class={styles.wrapBox}>
  2055. {ABC_DATA.clef.map((item) => (
  2056. <div
  2057. class={styles.topBtn}
  2058. onClick={() => handleChange({ type: "clef", value: item.value })}
  2059. >
  2060. <div class={[styles.btnImg]}>
  2061. <TheIcon iconClassName={item.icon} />
  2062. </div>
  2063. <div>{item.name}</div>
  2064. </div>
  2065. ))}
  2066. </div>
  2067. </CollapseItem>
  2068. <CollapseItem title="调号" name="key">
  2069. <div class={styles.wrapBox}>
  2070. {ABC_DATA.key.map((item) => (
  2071. <div
  2072. class={styles.topBtn}
  2073. onClick={() => handleChange({ type: "key", value: item.value })}
  2074. >
  2075. <div class={[styles.btnImg]}>
  2076. <TheIcon iconClassName={item.icon} />
  2077. </div>
  2078. <div>{item.name}</div>
  2079. </div>
  2080. ))}
  2081. </div>
  2082. </CollapseItem>
  2083. </Collapse>
  2084. </div>
  2085. <div class={styles.box}>
  2086. <div class={styles.titleBox}>
  2087. <div class={styles.titleName} style={{ width: "50%", margin: "0 auto" }}>
  2088. <NInput
  2089. onKeyup={(e: Event) => e.stopPropagation()}
  2090. v-model:value={data.musicName}
  2091. placeholder="曲谱名称"
  2092. />
  2093. </div>
  2094. <div style={{ width: "30%", margin: "10px 0 0 auto" }}>
  2095. <NInput
  2096. onKeyup={(e: Event) => e.stopPropagation()}
  2097. v-model:value={data.creator}
  2098. placeholder="曲谱作者"
  2099. />
  2100. </div>
  2101. </div>
  2102. <div id="paper"></div>
  2103. {!data.loading && (
  2104. <Keys
  2105. show={data.active ? true : false}
  2106. instrumentCode={abcData.abc.subjectCode}
  2107. onClick={(val) => handleChange(val)}
  2108. />
  2109. )}
  2110. {/* <textarea ref={textAreaRef} class={styles.value} id="abc"></textarea> */}
  2111. <div id="audio" style={{ display: "none" }}></div>
  2112. <div id="warnings"></div>
  2113. <p class="beat"></p>
  2114. <pre class="clicked-info"></pre>
  2115. <pre class="feedback"></pre>
  2116. <div id="container"></div>
  2117. {data.loadingAudioSrouce && (
  2118. <div class={styles.loading}>
  2119. <NSpin></NSpin>
  2120. </div>
  2121. )}
  2122. </div>
  2123. </div>
  2124. <div ref={downRef}></div>
  2125. <TheSetting v-model:show={popup.settingShow} />
  2126. {data.selectMeasures.state && (
  2127. <UseDraggable
  2128. initialValue={{ x: data.selectMeasures.x, y: data.selectMeasures.y }}
  2129. class={[styles.selectMearesBox, !popup.selectMearesShow && styles.selectMearesHidden]}
  2130. >
  2131. <div onKeyup={(e: Event) => e.stopPropagation()}>
  2132. <NSpace justify="space-between">
  2133. <div class={styles.btnLineTitle}>输入小节范围</div>
  2134. <NButton
  2135. circle
  2136. quaternary
  2137. size="small"
  2138. onClick={() => (popup.selectMearesShow = false)}
  2139. >
  2140. <NIcon size={16} component={<Close />} />
  2141. </NButton>
  2142. </NSpace>
  2143. <NSpace align="center" wrap={false} wrapItem={false}>
  2144. <div class={styles.mearesInput}>
  2145. <NInputNumber
  2146. min={1}
  2147. max={data.selectMeasures.max}
  2148. bordered={false}
  2149. placeholder="开始小节"
  2150. showButton={false}
  2151. onUpdate:value={(val) => handleSetSelectMeares(val, "start")}
  2152. ></NInputNumber>
  2153. -
  2154. <NInputNumber
  2155. min={data.selectMeasures.start}
  2156. max={data.selectMeasures.max}
  2157. bordered={false}
  2158. placeholder="结束小节"
  2159. showButton={false}
  2160. onUpdate:value={(val) => handleSetSelectMeares(val, "end")}
  2161. ></NInputNumber>
  2162. </div>
  2163. <div class={styles.topBtn}>
  2164. <NSpin show={data.loadingAudioSrouce} size="small">
  2165. <div
  2166. class={styles.btnImg}
  2167. onClick={() => togglePlay(data.playState ? "pause" : "play")}
  2168. >
  2169. <img
  2170. style={{ display: data.playState ? "" : "none" }}
  2171. class={styles.topBtnIcon}
  2172. src={getImage("icon_21_1.png")}
  2173. />
  2174. <img
  2175. style={{ display: data.playState ? "none" : "" }}
  2176. class={styles.topBtnIcon}
  2177. src={getImage("icon_21.png")}
  2178. />
  2179. </div>
  2180. </NSpin>
  2181. </div>
  2182. </NSpace>
  2183. </div>
  2184. </UseDraggable>
  2185. )}
  2186. </div>
  2187. <div class={styles.exportPng}>
  2188. <div id="exportPng"></div>
  2189. </div>
  2190. </>
  2191. );
  2192. },
  2193. });