instrument.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. // import { isSpecialMark, isSpeedKeyword, isGradientWords, GRADIENT_SPEED_RESET_TAG } from "./speed-tag"
  2. // import { isSpecialMark, isSpeedKeyword, isGradientWords, GRADIENT_SPEED_RESET_TAG } from "./speed-tag"
  3. export class StringUtil {
  4. public static StringContainsSeparatedWord(str: string, wordRegExString: string, ignoreCase = false): boolean {
  5. const regExp = new RegExp("( |^)" + wordRegExString + "([ .]|$)", ignoreCase ? "i" : undefined)
  6. return regExp.test(str)
  7. }
  8. }
  9. export const formatXML = (xml: string): string => {
  10. if (!xml) return ""
  11. const xmlParse = new DOMParser().parseFromString(xml, "text/xml")
  12. const measures: any = xmlParse.getElementsByTagName("measure")
  13. // const repeats: any = Array.from(xmlParse.querySelectorAll('repeat'))
  14. // 处理重复小节信息
  15. // let speed = -1
  16. let beats = -1
  17. let beatType = -1
  18. // 小节中如果没有节点默认为休止符
  19. for (const measure of measures) {
  20. if (beats === -1 && measure.getElementsByTagName("beats").length) {
  21. beats = parseInt(measure.getElementsByTagName("beats")[0].textContent || "4")
  22. }
  23. if (beatType === -1 && measure.getElementsByTagName("beat-type").length) {
  24. beatType = parseInt(measure.getElementsByTagName("beat-type")[0].textContent || "4")
  25. }
  26. // if (speed === -1 && measure.getElementsByTagName('per-minute').length) {
  27. // speed = parseInt(measure.getElementsByTagName('per-minute')[0].textContent || this.firstLib?.speed)
  28. // }
  29. const divisions = parseInt(measure.getElementsByTagName("divisions")[0]?.textContent || "256")
  30. if (measure.getElementsByTagName("note").length === 0) {
  31. const forwardTimeElement = measure.getElementsByTagName("forward")[0]?.getElementsByTagName("duration")[0]
  32. if (forwardTimeElement) {
  33. forwardTimeElement.textContent = "0"
  34. }
  35. measure.innerHTML =
  36. measure.innerHTML +
  37. `
  38. <note>
  39. <rest measure="yes"/>
  40. <duration>${divisions * beats}</duration>
  41. <voice>1</voice>
  42. <type>whole</type>
  43. </note>`
  44. }
  45. }
  46. return new XMLSerializer().serializeToString(xmlParse)
  47. }
  48. export const onlyVisible = (xml: string, partIndex: number): string => {
  49. if (!xml) return ""
  50. const xmlParse = new DOMParser().parseFromString(xml, "text/xml")
  51. const partList = xmlParse.getElementsByTagName("part-list")?.[0]?.getElementsByTagName("score-part") || []
  52. // @typescript-eslint/no-unused-vars
  53. const partListNames = Array.from(partList).map(item => item.getElementsByTagName("part-name")?.[0].textContent || "")
  54. const parts: any = xmlParse.getElementsByTagName("part")
  55. // const firstTimeInfo = parts[0]?.getElementsByTagName('metronome')[0]?.parentElement?.parentElement?.cloneNode(true)
  56. const part: any = parts[0]
  57. const firstMeasures = [...part.getElementsByTagName("measure")]
  58. const metronomes = [...part.getElementsByTagName("metronome")]
  59. // const words = [...part.getElementsByTagName("words")]
  60. // const codas = [...part.getElementsByTagName("coda")]
  61. // const rehearsals = [...part.getElementsByTagName("rehearsal")]
  62. /** 第一分谱如果是约定的配置分谱则跳过 */
  63. if (partListNames[0]?.toLocaleUpperCase?.() === "COMMON") {
  64. partIndex++
  65. partListNames.shift()
  66. }
  67. const visiblePartInfo = partList[partIndex]
  68. // console.log(visiblePartInfo, partIndex)
  69. if (visiblePartInfo) {
  70. const id = visiblePartInfo.getAttribute("id")
  71. Array.from(parts).forEach((part: any) => {
  72. if (part && part.getAttribute("id") !== id) {
  73. part.parentNode?.removeChild(part)
  74. // 不等于第一行才添加避免重复添加
  75. } else if (part && part.getAttribute("id") !== "P1") {
  76. // 速度标记仅保留最后一个
  77. const metronomeData: {
  78. [key in string]: Element
  79. } = {}
  80. for (let i = 0; i < metronomes.length; i++) {
  81. const metronome = metronomes[i]
  82. const metronomeContainer = metronome.parentElement?.parentElement?.parentElement
  83. if (metronomeContainer) {
  84. const index = firstMeasures.indexOf(metronomeContainer)
  85. metronomeData[index] = metronome
  86. }
  87. }
  88. Object.values(metronomeData).forEach(metronome => {
  89. const metronomeContainer: any = metronome.parentElement?.parentElement
  90. const parentMeasure: any = metronomeContainer?.parentElement
  91. const measureMetronomes = [...(parentMeasure?.childNodes || [])]
  92. const metronomesIndex = metronomeContainer ? measureMetronomes.indexOf(metronomeContainer) : -1
  93. // console.log(parentMeasure)
  94. if (parentMeasure && metronomesIndex > -1) {
  95. const index = firstMeasures.indexOf(parentMeasure)
  96. const activeMeasure = part.getElementsByTagName("measure")[index]
  97. setElementNoteBefore(metronomeContainer, parentMeasure, activeMeasure)
  98. }
  99. })
  100. }
  101. // 最后一个小节的结束线元素不在最后 调整
  102. if (part && part.getAttribute("id") === id) {
  103. const barlines = part.getElementsByTagName("barline")
  104. const lastParent = barlines[barlines.length - 1]?.parentElement
  105. if (lastParent?.lastElementChild?.tagName !== "barline") {
  106. const children: any = lastParent?.children || []
  107. for (const el of children) {
  108. if (el.tagName === "barline") {
  109. // 将结束线元素放到最后
  110. lastParent?.appendChild(el)
  111. break
  112. }
  113. }
  114. }
  115. }
  116. })
  117. Array.from(partList).forEach(part => {
  118. if (part && part.getAttribute("id") !== id) {
  119. part.parentNode?.removeChild(part)
  120. }
  121. })
  122. // 处理装饰音问题
  123. // const notes = xmlParse.getElementsByTagName("note")
  124. // const getNextvNoteDuration = (i: number) => {
  125. // let nextNote = notes[i + 1]
  126. // // 可能存在多个装饰音问题,取下一个非装饰音时值
  127. // for (let index = i index < notes.length index++) {
  128. // const note = notes[index]
  129. // if (!note.getElementsByTagName("grace")?.length) {
  130. // nextNote = note
  131. // break
  132. // }
  133. // }
  134. // const nextNoteDuration = nextNote?.getElementsByTagName("duration")[0]
  135. // return nextNoteDuration
  136. // }
  137. // Array.from(notes).forEach((note, i) => {
  138. // const graces = note.getElementsByTagName("grace")
  139. // if (graces && graces.length) {
  140. // // if (i !== 0) {
  141. // // note.appendChild(getNextvNoteDuration(i)?.cloneNode(true))
  142. // // }
  143. // }
  144. // })
  145. }
  146. // console.log(xmlParse)
  147. return new XMLSerializer().serializeToString(appoggianceFormate(xmlParse))
  148. }
  149. // 倚音后连音线
  150. export const appoggianceFormate = (xmlParse: Document): Document => {
  151. if (!xmlParse) return xmlParse
  152. const graces: any = xmlParse.querySelectorAll("grace")
  153. if (!graces.length) return xmlParse
  154. const getNextElement = (el: HTMLElement): HTMLElement => {
  155. if (el.querySelector("grace")) {
  156. return getNextElement(el?.nextElementSibling as HTMLElement)
  157. }
  158. return el
  159. }
  160. for (const grace of graces) {
  161. const notations = grace.parentElement?.querySelector("notations")
  162. if (notations && notations.querySelectorAll("slur").length > 1) {
  163. const nextEle: Element = getNextElement(grace.parentElement?.nextElementSibling as HTMLElement)
  164. if (nextEle && nextEle.querySelectorAll("slur").length > 0) {
  165. const slurNumber = Array.from(nextEle.querySelector("notations")?.children || []).map((el: Element) => {
  166. return el.getAttribute("number")
  167. })
  168. const slurs = notations.querySelectorAll("slur")
  169. for (const nota of slurs) {
  170. if (!slurNumber.includes(nota.getAttribute("number"))) {
  171. nextEle.querySelector("notations")?.appendChild(nota)
  172. }
  173. }
  174. }
  175. }
  176. }
  177. return xmlParse
  178. }
  179. /**
  180. * 添加第一分谱信息至当前分谱
  181. * @param ele 需要插入的元素
  182. * @param fitstParent 合奏谱第一个分谱
  183. * @param parent 需要添加的分谱
  184. */
  185. const setElementNoteBefore = (ele: Element, fitstParent: Element, parent?: Element | null) => {
  186. let noteIndex = 0
  187. if (!fitstParent) {
  188. return
  189. }
  190. for (let index = 0; index < fitstParent.childNodes.length; index++) {
  191. const element = fitstParent.childNodes[index]
  192. if (element.nodeName === "note") {
  193. noteIndex++
  194. }
  195. if (element === ele) {
  196. break
  197. }
  198. }
  199. if (noteIndex === 0 && parent) {
  200. parent.insertBefore(ele, parent.childNodes[0])
  201. return
  202. }
  203. if (parent && parent.childNodes.length > 0) {
  204. let noteIndex2 = 0
  205. const notes = Array.from(parent.childNodes).filter(child => child.nodeName === "note")
  206. const lastNote = notes[notes.length - 1]
  207. if (noteIndex >= notes.length && lastNote) {
  208. parent.insertBefore(ele, parent.childNodes[Array.from(parent.childNodes).indexOf(lastNote)])
  209. return
  210. }
  211. for (let index = 0; index < notes.length; index++) {
  212. const element = notes[index]
  213. if (element.nodeName === "note") {
  214. noteIndex2 = noteIndex2 + 1
  215. if (noteIndex2 === noteIndex) {
  216. parent.insertBefore(ele, element)
  217. break
  218. }
  219. }
  220. }
  221. }
  222. // console.log(noteIndex, parent)
  223. }
  224. /**
  225. * 检查传入文字是否为重复关键词
  226. * @param text 总谱xml
  227. * @returns 是否是重复关键词
  228. */
  229. export const isRepeatWord = (text: string): boolean => {
  230. if (text) {
  231. const innerText = text.toLocaleLowerCase()
  232. const dsRegEx = "d\\s?\\.s\\."
  233. const dcRegEx = "d\\.\\s?c\\."
  234. return (
  235. innerText === "@" ||
  236. StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + " al fine", true) ||
  237. StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + " al coda", true) ||
  238. StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + " al fine", true) ||
  239. StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + " al coda", true) ||
  240. StringUtil.StringContainsSeparatedWord(innerText, dcRegEx) ||
  241. StringUtil.StringContainsSeparatedWord(innerText, "da\\s?capo", true) ||
  242. StringUtil.StringContainsSeparatedWord(innerText, dsRegEx, true) ||
  243. StringUtil.StringContainsSeparatedWord(innerText, "dal\\s?segno", true) ||
  244. StringUtil.StringContainsSeparatedWord(innerText, "al\\s?coda", true) ||
  245. StringUtil.StringContainsSeparatedWord(innerText, "to\\s?coda", true) ||
  246. StringUtil.StringContainsSeparatedWord(innerText, "a (la )?coda", true) ||
  247. StringUtil.StringContainsSeparatedWord(innerText, "fine", true) ||
  248. StringUtil.StringContainsSeparatedWord(innerText, "coda", true) ||
  249. StringUtil.StringContainsSeparatedWord(innerText, "segno", true)
  250. )
  251. }
  252. return false
  253. }
  254. /** 从xml中获取自定义信息,并删除多余的字符串 */
  255. export const getCustomInfo = (xml: string): any => {
  256. const data = {
  257. showSpeed: true,
  258. parsedXML: xml
  259. }
  260. const xmlParse = new DOMParser().parseFromString(xml, "text/xml")
  261. const words: any = xmlParse.getElementsByTagName("words")
  262. for (const word of words) {
  263. if (word && word.textContent?.trim() === "隐藏速度") {
  264. data.showSpeed = false
  265. word.textContent = ""
  266. }
  267. if (word && word.textContent?.trim() === "@") {
  268. word.textContent = "segno"
  269. }
  270. }
  271. data.parsedXML = new XMLSerializer().serializeToString(xmlParse)
  272. return data
  273. }