index.tsx 78 KB

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