helpers.ts 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506
  1. import state from './state'
  2. import appState from '/src/state'
  3. import { browser } from '/src/helpers/utils'
  4. import runtime, { getFixTime } from './runtime'
  5. // @ts-ignore
  6. import {
  7. isSpecialMark,
  8. isSpeedKeyword,
  9. Fraction,
  10. SourceMeasure,
  11. isGradientWords,
  12. GRADIENT_SPEED_RESET_TAG,
  13. StringUtil,
  14. } from '/osmd-extended/src'
  15. import dayjs from 'dayjs'
  16. const browserInfo = browser()
  17. const getLinkId = (): string => {
  18. return location.hash.split('?')[0].split('/').pop() || ''
  19. }
  20. export const retain = (time: number) => {
  21. return Math.ceil(time * 1000000) / 1000000
  22. }
  23. export function formatBeatUnit(beatUnit: string) {
  24. let multiple = 4
  25. switch (beatUnit) {
  26. case '1024th':
  27. // bpm = bpm;
  28. multiple = 1024
  29. break
  30. case '512th':
  31. // divisionsFromNote = (noteDuration / 4) * 512;
  32. multiple = 512
  33. break
  34. case '256th':
  35. // divisionsFromNote = (noteDuration / 4) * 256;
  36. multiple = 256
  37. break
  38. case '128th':
  39. // divisionsFromNote = (noteDuration / 4) * 128;
  40. multiple = 128
  41. break
  42. case '64th':
  43. // divisionsFromNote = (noteDuration / 4) * 64;
  44. multiple = 64
  45. break
  46. case '32nd':
  47. // divisionsFromNote = (noteDuration / 4) * 32;
  48. multiple = 32
  49. break
  50. case '16th':
  51. // divisionsFromNote = (noteDuration / 4) * 16;
  52. multiple = 16
  53. break
  54. case 'eighth':
  55. // divisionsFromNote = (noteDuration / 4) * 8;
  56. multiple = 8
  57. break
  58. case 'quarter':
  59. multiple = 4
  60. break
  61. case 'half':
  62. // divisionsFromNote = (noteDuration / 4) * 2;
  63. multiple = 2
  64. break
  65. case 'whole':
  66. // divisionsFromNote = (noteDuration / 4);
  67. multiple = 1
  68. break
  69. case 'breve':
  70. // divisionsFromNote = (noteDuration / 4) / 2;
  71. multiple = 0.5
  72. break
  73. case 'long':
  74. // divisionsFromNote = (noteDuration / 4) / 4;
  75. multiple = 0.25
  76. break
  77. case 'maxima':
  78. // divisionsFromNote = (noteDuration / 4) / 8;
  79. multiple = 0.125
  80. break
  81. default:
  82. break
  83. }
  84. return multiple
  85. }
  86. export const formatLyricsEntries = (note: any) => {
  87. const voiceEntries = note.parentStaffEntry?.voiceEntries || []
  88. const lyricsEntries: string[] = []
  89. for (const voic of voiceEntries) {
  90. if (voic.lyricsEntries?.table) {
  91. const values: any[] = Object.values(voic.lyricsEntries.table)
  92. for (const lyric of values) {
  93. lyricsEntries.push(lyric?.value.text)
  94. }
  95. }
  96. }
  97. return lyricsEntries
  98. }
  99. export const getMeasureDurationDiff = (measure: any) => {
  100. const { realValue: SRealValue } = measure.activeTimeSignature
  101. const { realValue: RRealValue } = measure.duration
  102. return SRealValue - RRealValue
  103. }
  104. export type GradualChange = {
  105. resetXmlNoteIndex: number
  106. startXmlNoteIndex: number
  107. endXmlNoteIndex: number
  108. startWord: string
  109. }
  110. const speedInfo: { [key in string]: number } = {
  111. 'rall.': 1.333333333,
  112. 'poco rit.': 1.333333333,
  113. 'rit.': 1.333333333,
  114. 'molto rit.': 1.333333333,
  115. 'molto rall': 1.333333333,
  116. lentando: 1.333333333,
  117. allargando: 1.333333333,
  118. morendo: 1.333333333,
  119. 'accel.': 0.8,
  120. calando: 2,
  121. 'poco accel.': 0.8,
  122. }
  123. /** 按照dorico的渐快渐慢生成对应的速度 */
  124. export const createSpeedInfo = (gradualChange: GradualChange | undefined, speed: number) => {
  125. if (gradualChange && speedInfo[gradualChange.startWord?.toLocaleLowerCase()]) {
  126. const notenum = Math.max(gradualChange.endXmlNoteIndex, 3)
  127. const speeds: number[] = []
  128. const startSpeed = speed
  129. const endSpeed = speed / speedInfo[gradualChange.startWord?.toLocaleLowerCase()]
  130. for (let index = 0; index < notenum; index++) {
  131. const speed = startSpeed + ((endSpeed - startSpeed) / notenum) * (index + 1)
  132. speeds.push(speed)
  133. }
  134. return speeds
  135. }
  136. return
  137. }
  138. const tranTime = (str: string = '') => {
  139. let result = str
  140. const splits = str.split(':')
  141. if (splits.length === 1) {
  142. result = `00:${splits[0]}:00`
  143. } else if (splits.length === 2) {
  144. result = `00:${splits[0]}:${splits[1]}`
  145. }
  146. // console.log(`1970-01-01 00:${result}0`)
  147. return `1970-01-01 00:${result}0`
  148. }
  149. export const getAllNodes = (osmd: any) => {
  150. console.log(9999, osmd)
  151. const detailId = getLinkId()
  152. let fixtime = browserInfo.huawei ? 0.08 : 0 //getFixTime()
  153. const allNotes: any[] = []
  154. const allNoteId: string[] = []
  155. const allMeasures: any[] = []
  156. const { baseSpeed = 100 } = state
  157. const formatRealKey = (realKey: number, detail: any) => {
  158. // 长笛的LEVEL 2-5-1条练习是泛音练习,以每小节第一个音的指法为准,高音不变变指法。
  159. const olnyOneIds = ['906']
  160. if (olnyOneIds.includes(detailId)) {
  161. return detail.measures[0]?.realKey || realKey
  162. }
  163. // 圆号的LEVEL 2-5条练习是泛音练习,最后四小节指法以连音线第一个小节为准
  164. const olnyOneIds2 = ['782', '784']
  165. if (olnyOneIds2.includes(detailId)) {
  166. const measureNumbers = [14, 16, 30, 32]
  167. if (measureNumbers.includes(detail.firstVerticalMeasure?.measureNumber)) {
  168. return allNotes[allNotes.length - 1]?.realKey || realKey
  169. }
  170. }
  171. // 2-6 第三小节指法按照第一个音符显示
  172. const filterIds = ['900', '901', '640', '641', '739', '740', '800', '801', '773', '774', '869', '872', '714', '715']
  173. if (filterIds.includes(detailId)) {
  174. if (detail.firstVerticalMeasure?.measureNumber === 3 || detail.firstVerticalMeasure?.measureNumber === 9) {
  175. return detail.measures[0]?.realKey || realKey
  176. }
  177. }
  178. return realKey
  179. }
  180. if (state.gradualTimes) {
  181. console.log('合奏速度', state.gradual, state.gradualTimes)
  182. }
  183. if (osmd?.cursor) {
  184. try {
  185. osmd.cursor.reset()
  186. } catch (error) {}
  187. const iterator = osmd.cursor.iterator
  188. let i = 0
  189. let si = 0
  190. let measures: any[] = []
  191. let stepSpeeds: number[] = []
  192. let usetime = 0
  193. let relaMeasureLength = 0
  194. /** 弱起时间 */
  195. let difftime = 0
  196. let beatUnit = 'quarter'
  197. let useGradualTime = 0
  198. let gradualSpeed
  199. let gradualChange: GradualChange | undefined
  200. let gradualChangeIndex = 0
  201. let measureNumberPrinted = 1
  202. let indexOf = 0
  203. let staveIndex = 0
  204. let staveNoteIndex = 0
  205. let currentRealValueTotal = 0
  206. let skipNextNote = false
  207. let multipleRestMeasures = 0
  208. // const useedmeasures: Set<number> = new Set()
  209. while (!iterator.endReached) {
  210. // 为获取所有节点有修改源码currentVoiceEntries仅返回可见第一行,请搜索“修改为 仅根据当前可见声部第一行跳转”可找到位置
  211. // 多声部仅循环一次,以第一声部为准
  212. // console.log('Entries', {...osmd.cursor}, osmd.cursor.cursorElement)
  213. const cursorImg: HTMLElement = osmd.cursor.cursorElement
  214. const cursorBox: any = { move: false }
  215. if (cursorImg) {
  216. cursorBox.move = true
  217. cursorBox.x = cursorImg.offsetLeft
  218. cursorBox.y = cursorImg.offsetTop
  219. cursorBox.w = cursorImg.offsetWidth
  220. cursorBox.h = cursorImg.offsetHeight
  221. }
  222. const voiceEntries = iterator.currentVoiceEntries?.[0] ? [iterator.currentVoiceEntries?.[0]] : []
  223. const voiceEntries2 = iterator.currentVoiceEntries?.[1]
  224. let skipMode = false
  225. for (const v of voiceEntries) {
  226. // 始终只取第一个声部中第一个音符的时间
  227. let note: any = v.notes[0]
  228. if (['Piano'].includes(state.activeDetail?.code) || state.activeDetail?.musicSheetType == 'CONCERT') {
  229. let _notes = []
  230. try {
  231. _notes = iterator.currentVoiceEntries
  232. ?.map((_n: any) => _n.notes)
  233. .flat()
  234. .sort((a: any, b: any) => a.Length.realValue - b.Length.realValue)
  235. note = _notes[0]
  236. } catch (error) {}
  237. }
  238. if (note) {
  239. if (si === 0) {
  240. allMeasures.push(note.sourceMeasure)
  241. }
  242. if (si === 0 && state.isSpecialBookCategory) {
  243. for (const expression of note.sourceMeasure?.TempoExpressions) {
  244. if (expression?.InstantaneousTempo?.beatUnit) {
  245. // 取最后一个有效的tempo
  246. beatUnit = expression.InstantaneousTempo.beatUnit
  247. }
  248. }
  249. }
  250. measureNumberPrinted = note.sourceMeasure?.MeasureNumberXML
  251. // 判断是否是同一小节
  252. if (staveIndex == note.sourceMeasure?.MeasureNumberXML) {
  253. staveNoteIndex++
  254. } else {
  255. // staveIndex不同,重新赋值
  256. staveIndex = note.sourceMeasure?.MeasureNumberXML
  257. staveNoteIndex = 0
  258. }
  259. let measureSpeed = note.sourceMeasure.tempoInBPM
  260. const { metronomeNoteIndex } = iterator.currentMeasure
  261. if (metronomeNoteIndex !== 0 && metronomeNoteIndex > si) {
  262. measureSpeed = allNotes[allNotes.length - 1]?.speed || 100
  263. }
  264. const activeVerticalMeasureList = [note.sourceMeasure.verticalMeasureList?.[0]] || []
  265. const { realValue } = iterator.currentTimeStamp
  266. const { RealValue: vRealValue, Denominator: vDenominator } = formatDuration(
  267. iterator.currentMeasure.activeTimeSignature,
  268. iterator.currentMeasure.duration
  269. )
  270. let { wholeValue, numerator, denominator, realValue: noteRealValue } = note.length
  271. let relativeTime = usetime //realValue * 4 * (60 / measureSpeed)
  272. /**
  273. * 速度不能为0, 此处的速度应该是按照设置的速度而不是校准后的速度,否则mp3速度不对
  274. */
  275. let beatSpeed =
  276. (state.isSpecialBookCategory
  277. ? getTimeByBeatUnit(beatUnit, measureSpeed, iterator.currentMeasure.activeTimeSignature.Denominator)
  278. : baseSpeed) || 1
  279. let speed = (state.isSpecialBookCategory ? measureSpeed : baseSpeed) || 1
  280. // console.log('曲谱设置的速度', { base: getTimeByBeatUnit(beatUnit, measureSpeed, iterator.currentMeasure.activeTimeSignature.Denominator), beatSpeed, speed})
  281. gradualChange = iterator.currentMeasure.speedInfo || gradualChange
  282. gradualSpeed = osmd.sheet.soundTempos?.get(note.sourceMeasure.measureListIndex) || gradualSpeed
  283. if (!gradualSpeed || gradualSpeed.length < 2) {
  284. gradualSpeed = createSpeedInfo(gradualChange, speed)
  285. }
  286. /**
  287. * 渐变速度逻辑如下:
  288. * 1. 获取渐变关键词,记录开始区间、结束区间、渐变整体结束;
  289. * 2. 计算区间内每个小节最小音符时长单位,整除计算比例;
  290. * 3. 按照比例计算下一个音符的速度;
  291. * 4. 合奏曲目这里需要处理一下,因为音符位置可能不太相同,理想的情况是每个分谱指定结束时间.
  292. */
  293. let gradualLength = 0
  294. const measureListIndex = iterator.currentMeasure.measureListIndex
  295. if (state.gradualTimes && Object.keys(state.gradualTimes).length > 0) {
  296. const withInRangeNote = state.gradual.find((item, index) => {
  297. const nextItem: any = state.gradual[index + 1]
  298. return (
  299. item[0].measureIndex <= measureListIndex &&
  300. item[1]?.measureIndex! >= measureListIndex &&
  301. (!nextItem || nextItem?.[0].measureIndex !== measureListIndex)
  302. )
  303. })
  304. if (!withInRangeNote) {
  305. useGradualTime = 0
  306. }
  307. const [first, last] = withInRangeNote || []
  308. if (first && last) {
  309. // 小节数量
  310. const continuous = last.measureIndex - first.measureIndex
  311. // 开始小节内
  312. const inTheFirstMeasure = first.closedMeasureIndex == measureListIndex && si >= first.noteInMeasureIndex
  313. // 结束小节内
  314. const inTheLastMeasure = last.closedMeasureIndex === measureListIndex && si < last.noteInMeasureIndex
  315. // 范围内小节
  316. const inFiestOrLastMeasure =
  317. first.closedMeasureIndex !== measureListIndex && last.closedMeasureIndex !== measureListIndex
  318. if (inTheFirstMeasure || inTheLastMeasure || inFiestOrLastMeasure) {
  319. const startTime = state.gradualTimes[first.measureIndex]
  320. const endTime = state.gradualTimes[last.measureIndex]
  321. if (startTime && endTime) {
  322. const times =
  323. continuous - first.leftDuration / first.allDuration + last.leftDuration / last.allDuration
  324. const diff = dayjs(tranTime(endTime)).diff(dayjs(tranTime(startTime)), 'millisecond')
  325. gradualLength = ((noteRealValue / vRealValue / times) * diff) / 1000
  326. useGradualTime += gradualLength
  327. }
  328. }
  329. }
  330. } else if (
  331. gradualChange &&
  332. gradualSpeed &&
  333. (gradualChange.startXmlNoteIndex === si || gradualChangeIndex > 0)
  334. ) {
  335. const startSpeed = gradualSpeed[0] - (gradualSpeed[1] - gradualSpeed[0])
  336. const { resetXmlNoteIndex, endXmlNoteIndex } = gradualChange
  337. const noteDiff = endXmlNoteIndex
  338. let stepSpeed = (gradualSpeed[gradualSpeed.length - 1] - startSpeed) / noteDiff
  339. stepSpeed = note.DotsXml ? stepSpeed / 1.5 : stepSpeed
  340. if (gradualChangeIndex < noteDiff) {
  341. const tempSpeed = Math.ceil(speed + stepSpeed * gradualChangeIndex)
  342. let tmpSpeed = getTimeByBeatUnit(
  343. beatUnit,
  344. tempSpeed,
  345. iterator.currentMeasure.activeTimeSignature.Denominator
  346. )
  347. const maxLength = (wholeValue + numerator / denominator) * vDenominator * (60 / tmpSpeed)
  348. speed += Math.ceil(stepSpeed * (gradualChangeIndex + 1))
  349. tmpSpeed = getTimeByBeatUnit(beatUnit, speed, iterator.currentMeasure.activeTimeSignature.Denominator)
  350. const minLength = (wholeValue + numerator / denominator) * vDenominator * (60 / tmpSpeed)
  351. gradualLength = (maxLength + minLength) / 2
  352. } else if (resetXmlNoteIndex > gradualChangeIndex) {
  353. speed = allNotes[i - 1]?.speed
  354. }
  355. beatSpeed =
  356. (state.isSpecialBookCategory
  357. ? getTimeByBeatUnit(beatUnit, speed, iterator.currentMeasure.activeTimeSignature.Denominator)
  358. : baseSpeed) || 1
  359. const isEnd = !(gradualChangeIndex < noteDiff) && !(resetXmlNoteIndex > gradualChangeIndex)
  360. gradualChangeIndex++
  361. // console.log(gradualChangeIndex)
  362. if (isEnd) {
  363. gradualChangeIndex = 0
  364. gradualChange = undefined
  365. gradualSpeed = undefined
  366. stepSpeeds = []
  367. }
  368. }
  369. if (i === 0) {
  370. fixtime += getFixTime(beatSpeed)
  371. }
  372. // console.log({fixtime, relativeTime}, 99999999)
  373. // 酷乐秀计算音符时值方法
  374. // let noteLength =
  375. // (numerator === 0 && note.isRestFlag ? vRealValue : (wholeValue + numerator) / denominator) *
  376. // vDenominator *
  377. // (60 / beatSpeed)
  378. //管乐迷计算时值方法
  379. let noteLength = gradualLength
  380. ? gradualLength
  381. : Math.min(vRealValue, noteRealValue) * formatBeatUnit(beatUnit) * (60 / beatSpeed)
  382. const measureLength = vRealValue * vDenominator * (60 / beatSpeed)
  383. // 单独处理个别的声部
  384. if (['Piano'].includes(state.activeDetail?.code)) {
  385. const currentRealValue = iterator.currentTimeStamp.realValue - currentRealValueTotal
  386. noteLength =
  387. (currentRealValue || (numerator === 0 ? vRealValue : (wholeValue + numerator) / denominator)) *
  388. vDenominator *
  389. (60 / beatSpeed)
  390. }
  391. // 如果是休止符并且整个小节休止,休止符的时值小于小节时值,取小节的时值
  392. if (note.isRestFlag && note?.sourceMeasure?.allRests) {
  393. multipleRestMeasures = note?.sourceMeasure?.multipleRestMeasures
  394. }
  395. if (multipleRestMeasures > 0) {
  396. multipleRestMeasures -= 1
  397. noteLength = measureLength
  398. }
  399. // 如果休止符的时值大于小节的时值
  400. if (note.isRestFlag && noteLength > measureLength) {
  401. noteLength = measureLength
  402. }
  403. // 处理附点时长不正确问题
  404. if (note.DotsXml && note.tuplet) {
  405. console.log('处理附点时长不正确问题')
  406. noteLength = noteLength * 1.5
  407. }
  408. // 后倚音通过跳过的方式实现
  409. if (skipNextNote) {
  410. noteLength = 0.0000001
  411. skipNextNote = false
  412. skipMode = true
  413. }
  414. const Expressions = note.sourceMeasure.staffLinkedExpressions?.[0]
  415. for (const Expression of Expressions || []) {
  416. if (Expression) {
  417. const needSkip = Expression.expressions?.find((item: any) => item.label === '跳过下一个')
  418. if (needSkip && Fraction.Equal(note.voiceEntry?.Timestamp, Expression.Timestamp)) {
  419. skipNextNote = true
  420. break
  421. }
  422. }
  423. }
  424. currentRealValueTotal = iterator.currentTimeStamp.realValue
  425. usetime += noteLength
  426. relaMeasureLength += noteLength
  427. let relaEndtime = noteLength + relativeTime
  428. // console.log({noteLength,relativeTime ,relaEndtime, endtime: relaEndtime + fixtime})
  429. const fixedKey = note.ParentVoiceEntry.ParentVoice.Parent.SubInstruments[0].fixedKey || 0
  430. // const svgElelent = activeVerticalMeasureList[0]?.vfVoices['1']?.tickables[si]
  431. const svgElelent = activeVerticalMeasureList[0]?.vfVoices['1']?.tickables[staveNoteIndex]
  432. // console.log(relativeTime)
  433. if (allNotes.length && allNotes[allNotes.length - 1].relativeTime === relativeTime) {
  434. continue
  435. }
  436. // console.log(iterator.currentMeasure)
  437. // 如果是弱起就补齐缺省的时长
  438. if (i === 0) {
  439. let _firstMeasureRealValue = 0
  440. const staffEntries = note.sourceMeasure.verticalMeasureList?.[0]?.staffEntries || []
  441. //计算第一个小节里面的音符时值是否等于整个小节的时值
  442. staffEntries.forEach((_a: any) => {
  443. if(_a?.sourceStaffEntry?.voiceEntries?.[0]?.notes?.[0]?.length?.realValue){
  444. _firstMeasureRealValue += _a.sourceStaffEntry.voiceEntries[0].notes[0].length.realValue
  445. }
  446. })
  447. if (_firstMeasureRealValue < vRealValue){
  448. // console.log(_firstMeasureRealValue, vRealValue)
  449. // 如果是弱起,将整个小节的时值减去音符的时值,就是缺省的时值
  450. difftime = measureLength - noteLength
  451. }
  452. /** 如果是酷乐秀MIDI类型文件就不处理弱起 */
  453. if (state.activeDetail?.audioType === 'MIDI') {
  454. difftime = 0
  455. }
  456. fixtime += difftime
  457. }
  458. const nodeDetail = {
  459. fixtime,
  460. skipMode,
  461. NoteToGraphicalNoteObjectId: note.NoteToGraphicalNoteObjectId,
  462. cursorBox,
  463. skipNextNote,
  464. measureNumberPrinted,
  465. difftime,
  466. octaveOffset: activeVerticalMeasureList[0]?.octaveOffset,
  467. frequency: note.pitch?.frequency,
  468. speed,
  469. beatSpeed,
  470. i,
  471. si,
  472. stepSpeeds,
  473. indexOfMeasures: indexOf,
  474. measureOpenIndex: allMeasures.length - 1,
  475. measures, //: groupMeasures[indexOf],
  476. tempoInBPM: note.sourceMeasure.tempoInBPM,
  477. measureLength,
  478. relaMeasureLength,
  479. id: svgElelent?.attrs.id,
  480. note: note.halfTone + 12, // see issue #224
  481. relativeTime: retain(relativeTime),
  482. time: retain(relativeTime + fixtime),
  483. endtime: retain(relaEndtime + fixtime),
  484. relaEndtime: retain(relaEndtime),
  485. realValue,
  486. halfTone: note.halfTone,
  487. voiceEntry: {
  488. isStaccato: note.voiceEntry.isStaccato(),
  489. Timestamp: { ...note.voiceEntry.Timestamp },
  490. ornamentContainer: note.voiceEntry.ornamentContainer ? { ...note.voiceEntry.ornamentContainer } : '',
  491. },
  492. noteElement: {
  493. NoteToGraphicalNoteObjectId: note.NoteToGraphicalNoteObjectId,
  494. notehead: note.notehead
  495. ? {
  496. filled: note.notehead.filled,
  497. shape: note.notehead.shape,
  498. sourceNote: note.notehead.sourceNote?.NoteToGraphicalNoteObjectId,
  499. }
  500. : '',
  501. noteheadColor: note.noteheadColor,
  502. isRestFlag: note.isRestFlag,
  503. sourceMeasure: {
  504. measureListIndex: note?.SourceMeasure?.measureListIndex,
  505. MeasureNumberXML: note?.SourceMeasure?.MeasureNumberXML,
  506. allRests: note?.SourceMeasure?.allRests,
  507. isRestFlag: note?.SourceMeasure?.isRestFlag,
  508. multipleRestMeasures: note?.SourceMeasure?.multipleRestMeasures,
  509. verticalMeasureList: Array.isArray(note?.SourceMeasure?.verticalMeasureList)
  510. ? note.SourceMeasure.verticalMeasureList.map((v: any) => {
  511. const { x, y, width, height, start_x, end_x } = v?.stave || {}
  512. return v
  513. ? {
  514. stave: {
  515. x,
  516. y,
  517. width,
  518. height,
  519. start_x,
  520. end_x,
  521. },
  522. boundingBox:
  523. v && v.boundingBox
  524. ? {
  525. absolutePosition: { ...v.boundingBox.absolutePosition },
  526. size: { ...v.boundingBox.size },
  527. }
  528. : '',
  529. }
  530. : undefined
  531. })
  532. : [],
  533. activeTimeSignature: { ...note.activeTimeSignature },
  534. },
  535. tie: note.tie
  536. ? {
  537. StartNote: {
  538. NoteToGraphicalNoteObjectId: note.tie.StartNote.NoteToGraphicalNoteObjectId,
  539. },
  540. notes:
  541. (Array.isArray(note.tie.notes) &&
  542. note.tie.notes?.map((_tie: any) => {
  543. return {
  544. NoteToGraphicalNoteObjectId: _tie.NoteToGraphicalNoteObjectId,
  545. }
  546. })) ||
  547. [],
  548. }
  549. : '',
  550. slurs: Array.isArray(note.slurs)
  551. ? note.slurs.map((slur: any) => {
  552. return {
  553. startNote: {
  554. NoteToGraphicalNoteObjectId: slur.startNote.NoteToGraphicalNoteObjectId,
  555. },
  556. endNote: {
  557. NoteToGraphicalNoteObjectId: slur.endNote.NoteToGraphicalNoteObjectId,
  558. },
  559. }
  560. })
  561. : [],
  562. pitch: {
  563. prevFrequency: note.pitch?.prevFrequency,
  564. nextFrequency: note.pitch?.nextFrequency,
  565. frequency: note.pitch?.frequency,
  566. },
  567. Length: {
  568. ...note.Length,
  569. },
  570. },
  571. svgElelent: svgElelent
  572. ? {
  573. bbox: svgElelent.getBoundingBox?.() || '',
  574. top_y: svgElelent.top_y,
  575. note_height: svgElelent.note_height,
  576. }
  577. : '',
  578. fixedKey,
  579. realKey: 0,
  580. duration: 0,
  581. formatLyricsEntries: formatLyricsEntries(note),
  582. stave:
  583. activeVerticalMeasureList[0] && activeVerticalMeasureList[0].stave
  584. ? {
  585. attrs: activeVerticalMeasureList[0].stave ? { ...activeVerticalMeasureList[0].stave.attrs } : {},
  586. }
  587. : '',
  588. firstVerticalMeasure: { measureNumber: activeVerticalMeasureList?.[0]?.measureNumber },
  589. noteLength: 1,
  590. halfTone1: Array.isArray(v.notes) ? v.notes.map((n: any) => n.halfTone + 12).filter(Boolean) : [],
  591. halfTone2:
  592. voiceEntries2 && Array.isArray(voiceEntries2.notes)
  593. ? voiceEntries2.notes.map((n: any) => n.halfTone + 12).filter(Boolean)
  594. : [],
  595. }
  596. nodeDetail.realKey = formatRealKey(note.halfTone - fixedKey * 12, nodeDetail)
  597. nodeDetail.duration = nodeDetail.endtime - nodeDetail.time
  598. const tickables = activeVerticalMeasureList[0]?.vfVoices['1']?.tickables || []
  599. const sublength = note.sourceMeasure.verticalMeasureList?.[0]?.staffEntries?.length || tickables.length
  600. nodeDetail.noteLength = sublength || 1
  601. allNotes.push(nodeDetail)
  602. allNoteId.push(nodeDetail.id)
  603. measures.push({
  604. realKey: nodeDetail.realKey,
  605. NoteToGraphicalNoteObjectId: note.NoteToGraphicalNoteObjectId,
  606. })
  607. if (si < sublength - 1) {
  608. si++
  609. } else {
  610. si = 0
  611. relaMeasureLength = 0
  612. measures = []
  613. }
  614. }
  615. }
  616. // iterator.moveToNextVisibleVoiceEntry(false)
  617. osmd.cursor.next()
  618. i++
  619. }
  620. try {
  621. osmd.cursor.reset()
  622. } catch (error) {}
  623. }
  624. // 按照时间轴排序
  625. // console.log('看看👀', allNotes)
  626. const sortArray = allNotes
  627. .sort((a, b) => a.relativeTime - b.relativeTime)
  628. .map((item, index) => ({ ...item, i: index }))
  629. // for (let i = 0; i < sortArray.length; i++) {
  630. // const note = { ...sortArray[i] }
  631. // const prevNote = sortArray[i - 1]
  632. // const isNotNeedStop = note.noteElement.tie && prevNote?.noteElement.tie && note.halfTone === prevNote?.halfTone
  633. // const isOvertone = false
  634. // if (prevNote) {
  635. // if (isNotNeedStop || isOvertone) {
  636. // note.sourceStartTime = note.time
  637. // note.sourceRelativeTime = note.relativeTime
  638. // note.sourceRealValue = note.realValue
  639. // note.sourceEndTime = note.endtime
  640. // note.sourceRelaEndtime = note.relaEndtime
  641. // note.relativeTime = prevNote.relativeTime
  642. // note.realValue = prevNote.realValue
  643. // note.time = prevNote.time
  644. // note.endtime = prevNote.endtime
  645. // note.relaEndtime = prevNote.relaEndtime
  646. // }
  647. // // 此处会导致休止符继续上一个音的指法
  648. // if (note.halfTone === 0) {
  649. // note.realKey = prevNote.realKey
  650. // }
  651. // }
  652. // sortArray[i] = note
  653. // }
  654. // console.log(sortArray)
  655. return sortArray
  656. }
  657. export const getAllNoteElements = (osmd: any) => {
  658. const list: any[] = []
  659. const listById: {
  660. [key: string]: any
  661. } = {}
  662. for (const measure of osmd.drawer.graphicalMusicSheet.measureList) {
  663. const activeMeasure = measure[0]
  664. for (const tickable of activeMeasure.vfVoices['1'].tickables) {
  665. list.push(tickable)
  666. listById[tickable.attrs.id] = tickable
  667. }
  668. }
  669. return {
  670. list,
  671. listById,
  672. }
  673. }
  674. export const setStepIndex = (osmd: any, num: number, prev?: number) => {
  675. if (osmd.product) {
  676. if (num || num === 0) {
  677. // console.log(prev, num)
  678. if (prev && num - prev === 1) {
  679. osmd.cursor.setPosition({ ...state.times[num].cursorBox })
  680. } else if (prev && num - prev > 0) {
  681. while (num - prev > 0) {
  682. prev++
  683. osmd.cursor.setPosition({ ...state.times[prev].cursorBox })
  684. }
  685. } else {
  686. osmd.cursor.setPosition({ ...state.times[num].cursorBox })
  687. }
  688. }
  689. } else {
  690. if (num || num === 0) {
  691. // console.log(prev, num)
  692. if (prev && num - prev === 1) {
  693. osmd.cursor.next()
  694. } else if (prev && num - prev > 0) {
  695. while (num - prev > 0) {
  696. prev++
  697. num - prev > 0
  698. osmd.cursor.next()
  699. }
  700. } else {
  701. let i = 0
  702. osmd.cursor.reset()
  703. while (i < num) {
  704. i++
  705. if (osmd.cursor.hidden !== false) {
  706. osmd.cursor.show()
  707. } else {
  708. // console.log(i, num)
  709. osmd.cursor.next()
  710. }
  711. }
  712. }
  713. }
  714. }
  715. }
  716. export const getIndex = (times: any[], currentTime: Number) => {
  717. // console.log(currentTime)
  718. if (currentTime > state.times[state.times.length - 1].endtime) {
  719. return -1
  720. }
  721. let index = 0
  722. const ftime = times.filter((n, fi) => {
  723. const p: any = times[fi - 1]
  724. return p?.skipNextNote === false
  725. })
  726. for (let i = 0; i < ftime.length; i++) {
  727. const item = ftime[i]
  728. const prevItem = ftime[i - 1]
  729. if (currentTime >= item.time) {
  730. if (!prevItem || item.time != prevItem.time) {
  731. index = item.i
  732. }
  733. } else {
  734. break
  735. }
  736. }
  737. if (state.sectionStatus && state.section.length === 2) {
  738. // 限制不超过此范围
  739. const startSection = state.befireSection || state.section[0]
  740. index = Math.min(Math.max(index, startSection.i), state.section[1].i)
  741. // console.log('endIndex', index)
  742. }
  743. return index
  744. }
  745. export const getSlursNote = (_note: any, pos?: 'start' | 'end') => {
  746. const note: any =
  747. state.times.find((n: any) => n.NoteToGraphicalNoteObjectId == _note.NoteToGraphicalNoteObjectId) || {}
  748. let itemNote = pos === 'end' ? note.noteElement.slurs[0]?.endNote : note.noteElement.slurs[0]?.startNote
  749. // console.log("🚀 ~ itemNote", itemNote, note)
  750. if (!itemNote) return undefined
  751. return state.times.find((n: any) => n.NoteToGraphicalNoteObjectId == itemNote.NoteToGraphicalNoteObjectId)
  752. }
  753. export const getNoteBySlursStart = (note: any, anyNoteHasSlurs?: boolean, pos?: 'start' | 'end') => {
  754. let activeNote = note
  755. let slursNote = getSlursNote(activeNote, pos)
  756. if (!slursNote && anyNoteHasSlurs) {
  757. for (const _item of activeNote.measures) {
  758. const item = state.times.find((n: any) => n.NoteToGraphicalNoteObjectId == _item.NoteToGraphicalNoteObjectId)
  759. // console.log("🚀 ~ item", item)
  760. if (item.noteElement.slurs.length) {
  761. slursNote = getSlursNote(item, pos)
  762. activeNote = item
  763. }
  764. }
  765. }
  766. if (activeNote && slursNote !== activeNote.noteElement) {
  767. // time = activeNote.time
  768. for (const note of state.times) {
  769. if (slursNote === note.noteElement) {
  770. return note
  771. }
  772. }
  773. }
  774. return activeNote
  775. }
  776. /** 根据 noteElement 获取note */
  777. export const getParentNote = (note: any) => {
  778. let parentNote
  779. if (note) {
  780. // time = activeNote.time
  781. for (const n of state.times) {
  782. if (note.NoteToGraphicalNoteObjectId === n.noteElement.NoteToGraphicalNoteObjectId) {
  783. // console.log(note)
  784. return n
  785. }
  786. }
  787. }
  788. return parentNote
  789. }
  790. /** 获取小节之间的连音线,仅同音高*/
  791. export const getNoteByMeasuresSlursStart = (note: any) => {
  792. let activeNote = note
  793. let tieNote
  794. // console.log(note.noteElement)
  795. if (note.noteElement.tie && note.noteElement.tie.StartNote) {
  796. tieNote = note.noteElement.tie.StartNote
  797. }
  798. if (activeNote && tieNote && tieNote !== activeNote.noteElement) {
  799. // time = activeNote.time
  800. for (const note of state.times) {
  801. if (tieNote.NoteToGraphicalNoteObjectId === note.noteElement.NoteToGraphicalNoteObjectId) {
  802. // console.log(note)
  803. return note
  804. }
  805. }
  806. }
  807. return activeNote
  808. }
  809. export const getActtiveNoteByTimes = (evt: MouseEvent) => {
  810. const el = (evt.target as HTMLDivElement)?.dataset
  811. // console.log(state)
  812. const data: any = {}
  813. for (const time of state.times) {
  814. if (time.id) {
  815. data[time.id] = time
  816. }
  817. }
  818. const activeNote = data[el.id || '']
  819. return activeNote
  820. }
  821. const getPrevHasSourceNote = (note: any) => {
  822. const indexOf = Math.max(state.times.indexOf(note) - 1, 0)
  823. for (let index = indexOf; index >= 0; index--) {
  824. const item = state.times[index]
  825. if (item?.stave) {
  826. return item
  827. }
  828. }
  829. }
  830. export const getBoundingBoxByverticalNote = (note: any) => {
  831. let measures = note?.noteElement?.sourceMeasure?.verticalMeasureList
  832. measures =
  833. !measures || !measures[0]
  834. ? note?.noteElement?.isRestFlag && getPrevHasSourceNote(note)?.noteElement?.sourceMeasure?.verticalMeasureList
  835. : measures
  836. let height = 0
  837. if (measures) {
  838. const firstMeasure = measures[runtime.partIndex]
  839. for (let index = 0; index < measures.length; index++) {
  840. const measure = measures[index]
  841. if (measure?.stave) {
  842. const { height: measureHeight } = measure?.stave
  843. if (index > 0) {
  844. height += measures[index - 1]?.stave.height
  845. }
  846. height += measureHeight
  847. const { x, y, width, context, start_x, end_x } = firstMeasure?.stave
  848. return {
  849. measureIndex: note?.noteElement?.sourceMeasure.measureListIndex || 0,
  850. MeasureNumberXML: note?.noteElement?.sourceMeasure.MeasureNumberXML || 1,
  851. start_x,
  852. end_x,
  853. height,
  854. x,
  855. y,
  856. width,
  857. context,
  858. }
  859. }
  860. }
  861. }
  862. return {
  863. measureIndex: 0,
  864. height,
  865. start_x: 0,
  866. end_x: 0,
  867. x: 0,
  868. y: 0,
  869. width: 0,
  870. context: {
  871. element: null,
  872. },
  873. }
  874. }
  875. export const getDuration = (osmd: any): any => {
  876. if (osmd) {
  877. const firstMeasure = osmd?.graphic?.measureList[0][0]
  878. // console.log(osmd?.graphic?.measureList[0][0]?.parentSourceMeasure)
  879. if (firstMeasure) {
  880. const { duration, tempoInBPM, activeTimeSignature, TempoExpressions } = firstMeasure?.parentSourceMeasure
  881. if (duration) {
  882. let beatUnit = 'quarter'
  883. for (const item of TempoExpressions) {
  884. beatUnit = item.InstantaneousTempo.beatUnit || 'quarter'
  885. }
  886. return {
  887. ...formatDuration(activeTimeSignature, duration),
  888. tempoInBPM,
  889. beatUnit,
  890. }
  891. }
  892. }
  893. }
  894. return {}
  895. }
  896. export const formatDuration = (activeTimeSignature: Fraction, duration: Fraction): Fraction => {
  897. // 弱起第一小节duration不对
  898. return activeTimeSignature
  899. }
  900. /** 根据音符单位,速度,几几拍计算正确的时间 */
  901. export const getTimeByBeatUnit = (beatUnit: string, bpm: number, denominator: number) => {
  902. let multiple = 4
  903. switch (beatUnit) {
  904. case '1024th':
  905. // bpm = bpm;
  906. multiple = 1024
  907. break
  908. case '512th':
  909. // divisionsFromNote = (noteDuration / 4) * 512;
  910. multiple = 512
  911. break
  912. case '256th':
  913. // divisionsFromNote = (noteDuration / 4) * 256;
  914. multiple = 256
  915. break
  916. case '128th':
  917. // divisionsFromNote = (noteDuration / 4) * 128;
  918. multiple = 128
  919. break
  920. case '64th':
  921. // divisionsFromNote = (noteDuration / 4) * 64;
  922. multiple = 64
  923. break
  924. case '32nd':
  925. // divisionsFromNote = (noteDuration / 4) * 32;
  926. multiple = 32
  927. break
  928. case '16th':
  929. // divisionsFromNote = (noteDuration / 4) * 16;
  930. multiple = 16
  931. break
  932. case 'eighth':
  933. // divisionsFromNote = (noteDuration / 4) * 8;
  934. multiple = 8
  935. break
  936. case 'quarter':
  937. multiple = 4
  938. break
  939. case 'half':
  940. // divisionsFromNote = (noteDuration / 4) * 2;
  941. multiple = 2
  942. break
  943. case 'whole':
  944. // divisionsFromNote = (noteDuration / 4);
  945. multiple = 1
  946. break
  947. case 'breve':
  948. // divisionsFromNote = (noteDuration / 4) / 2;
  949. multiple = 0.5
  950. break
  951. case 'long':
  952. // divisionsFromNote = (noteDuration / 4) / 4;
  953. multiple = 0.25
  954. break
  955. case 'maxima':
  956. // divisionsFromNote = (noteDuration / 4) / 8;
  957. multiple = 0.125
  958. break
  959. default:
  960. break
  961. }
  962. return (denominator / multiple) * bpm
  963. }
  964. /** 获取第一个小节的节拍速度,替换初始的速度 */
  965. export const getFirstBpmByMeasures = (measures: SourceMeasure[]) => {
  966. for (const item of measures) {
  967. if (item.TempoInBPM || (item as any).tempoInBPM) {
  968. return getMeasureRealBpm(item)
  969. }
  970. }
  971. return 90
  972. }
  973. /**
  974. * 根据小节获取当前节拍下真实的速度
  975. * 如: 6/8拍 4分音符bpm速度为120 则计算出8分音符的速度为120 * 2
  976. */
  977. export const getMeasureRealBpm = (measure: SourceMeasure) => {
  978. let bpm = measure.TempoInBPM
  979. let tempoExpression = null
  980. let activeTimeSignature: Fraction | null = null
  981. for (const expression of measure?.TempoExpressions) {
  982. if (expression?.InstantaneousTempo.beatUnit) {
  983. // 取最后一个有效的tempo
  984. tempoExpression = expression
  985. activeTimeSignature = measure.ActiveTimeSignature
  986. }
  987. }
  988. if (tempoExpression && activeTimeSignature) {
  989. bpm = tempoExpression?.InstantaneousTempo.TempoInBpm
  990. // let multiple = 1
  991. // console.log(tempoExpression.InstantaneousTempo.beatUnit)
  992. bpm = getTimeByBeatUnit(tempoExpression.InstantaneousTempo.beatUnit, bpm, activeTimeSignature.Denominator)
  993. }
  994. return bpm
  995. }
  996. export const getEnvHostname = () => {
  997. if (location.origin.indexOf('online') > -1) {
  998. return 'https://mstuonline.dayaedu.com'
  999. } else if (location.origin.indexOf('dev') > -1) {
  1000. return 'http://mstudev.dayaedu.com'
  1001. }
  1002. return 'https://mstutest.dayaedu.com'
  1003. }
  1004. export const getTvIconUrl = () => {
  1005. if (location.origin.indexOf('online') > -1) {
  1006. return 'https://mteaonline.dayaedu.com/#/guide'
  1007. } else if (location.origin.indexOf('dev') > -1) {
  1008. return 'http://mteadev.dayaedu.com/#/guide'
  1009. }
  1010. return 'https://mteatest.dayaedu.com/#/guide'
  1011. }
  1012. export const setPrefix = (url: string): string => {
  1013. if (url) {
  1014. return '?' + url
  1015. }
  1016. return ''
  1017. }
  1018. export type InitXmlInfo = {
  1019. title?: string
  1020. }
  1021. export const formatXML = (xml: string, initInfo?: InitXmlInfo): string => {
  1022. if (!xml) return ''
  1023. const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
  1024. const measures = xmlParse.getElementsByTagName('measure')
  1025. let beats = -1
  1026. let beatType = -1
  1027. /** 创建默认速度标记,避免无速度导致问题 */
  1028. // const defaultSpeedTag = document.createElement('direction')
  1029. // const defaultSpeedTagString = `<direction-type>
  1030. // <metronome default-y="30" color="#000000" font-family="Opus Text Std" font-style="normal" font-size="2.0153" font-weight="normal">
  1031. // <beat-unit>quarter</beat-unit>
  1032. // <per-minute>100</per-minute>
  1033. // </metronome>
  1034. // </direction-type>
  1035. // <voice>1</voice>
  1036. // <staff>1</staff>`
  1037. // defaultSpeedTag.innerHTML = defaultSpeedTagString
  1038. // if (xmlParse.getElementsByTagName('per-minute').length === 0) {
  1039. // measures[0]?.insertAdjacentElement('afterbegin', defaultSpeedTag)
  1040. // }
  1041. // 小节中如果没有节点默认为休止符
  1042. for (const measure of measures) {
  1043. if (beats === -1 && measure.getElementsByTagName('beats').length) {
  1044. beats = parseInt(measure.getElementsByTagName('beats')[0].textContent || '4')
  1045. }
  1046. if (beatType === -1 && measure.getElementsByTagName('beat-type').length) {
  1047. beatType = parseInt(measure.getElementsByTagName('beat-type')[0].textContent || '4')
  1048. }
  1049. // if (speed === -1 && measure.getElementsByTagName('per-minute').length) {
  1050. // speed = parseInt(measure.getElementsByTagName('per-minute')[0].textContent || this.firstLib?.speed)
  1051. // }
  1052. const divisions = parseInt(measure.getElementsByTagName('divisions')[0]?.textContent || '256')
  1053. if (measure.getElementsByTagName('note').length === 0) {
  1054. const forwardTimeElement = measure.getElementsByTagName('forward')[0]?.getElementsByTagName('duration')[0]
  1055. if (forwardTimeElement) {
  1056. forwardTimeElement.textContent = '0'
  1057. }
  1058. measure.innerHTML =
  1059. measure.innerHTML +
  1060. `
  1061. <note>
  1062. <rest measure="yes"/>
  1063. <duration>${divisions * beats}</duration>
  1064. <voice>1</voice>
  1065. <type>whole</type>
  1066. </note>`
  1067. }
  1068. }
  1069. if (initInfo) {
  1070. const workTitle = xmlParse.querySelector('work-title')
  1071. if (workTitle && initInfo.title) {
  1072. workTitle.textContent = initInfo.title
  1073. }
  1074. }
  1075. return new XMLSerializer().serializeToString(xmlParse)
  1076. }
  1077. export type CustomInfo = {
  1078. showSpeed: boolean
  1079. parsedXML: string
  1080. code: string
  1081. }
  1082. /** 从xml中获取自定义信息,并删除多余的字符串 */
  1083. export const getCustomInfo = (xml: string): CustomInfo => {
  1084. const data = {
  1085. showSpeed: true,
  1086. parsedXML: xml,
  1087. code: '',
  1088. }
  1089. const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
  1090. const words = xmlParse.getElementsByTagName('words')
  1091. for (const word of words) {
  1092. if (word && word.textContent?.trim() === '隐藏速度') {
  1093. data.showSpeed = false
  1094. word.textContent = ''
  1095. }
  1096. if (word && word.textContent?.trim() === '@') {
  1097. word.textContent = 'segno'
  1098. }
  1099. }
  1100. data.parsedXML = new XMLSerializer().serializeToString(xmlParse)
  1101. data.code = xmlParse.querySelector('part-name')?.innerHTML || ''
  1102. return data
  1103. }
  1104. /**
  1105. * 替换文本标签中的内容
  1106. */
  1107. const replaceTextConent = (beforeText: string, afterText: string, ele: Element): Element => {
  1108. const words = ele?.getElementsByTagName('words')
  1109. for (const word of words) {
  1110. if (word && word.textContent?.trim() === beforeText) {
  1111. word.textContent = afterText
  1112. }
  1113. }
  1114. return ele
  1115. }
  1116. /**
  1117. * 添加第一分谱信息至当前分谱
  1118. * @param ele 需要插入的元素
  1119. * @param fitstParent 合奏谱第一个分谱
  1120. * @param parent 需要添加的分谱
  1121. */
  1122. const setElementNoteBefore = (ele: Element, fitstParent: Element, parent?: Element | null) => {
  1123. let noteIndex: number = 0
  1124. if (!fitstParent) {
  1125. return
  1126. }
  1127. for (let index = 0; index < fitstParent.childNodes.length; index++) {
  1128. const element = fitstParent.childNodes[index]
  1129. if (element.nodeName === 'note') {
  1130. noteIndex++
  1131. }
  1132. if (element === ele) {
  1133. break
  1134. }
  1135. }
  1136. if (noteIndex === 0 && parent) {
  1137. parent.insertBefore(ele, parent.childNodes[0])
  1138. return
  1139. }
  1140. if (parent && parent.childNodes.length > 0) {
  1141. let noteIndex2: number = 0
  1142. for (let index = 0; index < parent.childNodes.length; index++) {
  1143. const element = parent.childNodes[index]
  1144. if (element.nodeName === 'note') {
  1145. noteIndex2 = noteIndex2 + 1
  1146. if (noteIndex2 === noteIndex) {
  1147. parent.insertBefore(ele, element)
  1148. break
  1149. }
  1150. }
  1151. }
  1152. }
  1153. // console.log(noteIndex, parent)
  1154. }
  1155. /**
  1156. * 检查传入文字是否为重复关键词
  1157. * @param text 总谱xml
  1158. * @returns 是否是重复关键词
  1159. */
  1160. export const isRepeatWord = (text: string): boolean => {
  1161. if (text) {
  1162. const innerText = text.toLocaleLowerCase()
  1163. const dsRegEx: string = 'd\\s?\\.s\\.'
  1164. const dcRegEx: string = 'd\\.\\s?c\\.'
  1165. return (
  1166. innerText === '@' ||
  1167. StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + ' al fine', true) ||
  1168. StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + ' al coda', true) ||
  1169. StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + ' al fine', true) ||
  1170. StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + ' al coda', true) ||
  1171. StringUtil.StringContainsSeparatedWord(innerText, dcRegEx) ||
  1172. StringUtil.StringContainsSeparatedWord(innerText, 'da\\s?capo', true) ||
  1173. StringUtil.StringContainsSeparatedWord(innerText, dsRegEx, true) ||
  1174. StringUtil.StringContainsSeparatedWord(innerText, 'dal\\s?segno', true) ||
  1175. StringUtil.StringContainsSeparatedWord(innerText, 'al\\s?coda', true) ||
  1176. StringUtil.StringContainsSeparatedWord(innerText, 'to\\s?coda', true) ||
  1177. StringUtil.StringContainsSeparatedWord(innerText, 'a (la )?coda', true) ||
  1178. StringUtil.StringContainsSeparatedWord(innerText, 'fine', true) ||
  1179. StringUtil.StringContainsSeparatedWord(innerText, 'coda', true) ||
  1180. StringUtil.StringContainsSeparatedWord(innerText, 'segno', true)
  1181. )
  1182. }
  1183. return false
  1184. }
  1185. export const onlyVisible = (xml: string, partIndex: number): string => {
  1186. if (!xml) return ''
  1187. const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
  1188. const partList = xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
  1189. const partListNames = Array.from(partList).map(
  1190. (item) => item.getElementsByTagName('part-name')?.[0].textContent || ''
  1191. )
  1192. const parts = xmlParse.getElementsByTagName('part') || []
  1193. if (!parts.length) {
  1194. // throw new Error('')
  1195. return ''
  1196. }
  1197. // const firstTimeInfo = parts[0]?.getElementsByTagName('metronome')[0]?.parentElement?.parentElement?.cloneNode(true)
  1198. const firstMeasures = [...parts[0]?.getElementsByTagName('measure')]
  1199. const metronomes = [...parts[0]?.getElementsByTagName('metronome')]
  1200. const words = [...parts[0]?.getElementsByTagName('words')]
  1201. const rehearsals = [...parts[0]?.getElementsByTagName('rehearsal')]
  1202. /** 第一分谱如果是约定的配置分谱则跳过 */
  1203. if (partListNames[0]?.toLocaleUpperCase?.() === "COMMON") {
  1204. partIndex++;
  1205. partListNames.shift();
  1206. }
  1207. const visiblePartInfo = partList[partIndex]
  1208. state.partListNames = partListNames
  1209. if (visiblePartInfo) {
  1210. const id = visiblePartInfo.getAttribute('id')
  1211. Array.from(parts).forEach((part) => {
  1212. if (part && part.getAttribute('id') !== id) {
  1213. part.parentNode?.removeChild(part)
  1214. // 不等于第一行才添加避免重复添加
  1215. } else if (part && part.getAttribute('id') !== 'P1') {
  1216. // 速度标记仅保留最后一个
  1217. const metronomeData: {
  1218. [key in string]: Element
  1219. } = {}
  1220. for (let i = 0; i < metronomes.length; i++) {
  1221. const metronome = metronomes[i]
  1222. const metronomeContainer = metronome.parentElement?.parentElement?.parentElement
  1223. if (metronomeContainer) {
  1224. const index = firstMeasures.indexOf(metronomeContainer)
  1225. metronomeData[index] = metronome
  1226. }
  1227. }
  1228. Object.values(metronomeData).forEach((metronome) => {
  1229. const metronomeContainer = metronome.parentElement?.parentElement
  1230. const parentMeasure = metronomeContainer?.parentElement
  1231. const measureMetronomes = [...(parentMeasure?.childNodes || [])]
  1232. const metronomesIndex = metronomeContainer ? measureMetronomes.indexOf(metronomeContainer) : -1
  1233. // console.log(parentMeasure)
  1234. if (parentMeasure && metronomesIndex > -1) {
  1235. const index = firstMeasures.indexOf(parentMeasure)
  1236. const activeMeasure = part.getElementsByTagName('measure')[index]
  1237. setElementNoteBefore(metronomeContainer, parentMeasure, activeMeasure)
  1238. // console.log(measureMetronomes, metronomesIndex, activeMeasure?.childNodes, activeMeasure?.childNodes[metronomesIndex])
  1239. // activeMeasure?.insertBefore(metronomeContainer.cloneNode(true), activeMeasure?.childNodes[metronomesIndex])
  1240. // // part.getElementsByTagName('measure')[index]?.appendChild(metronomeContainer.cloneNode(true))
  1241. // // console.log(index, parentMeasure, firstMeasures.indexOf(parentMeasure))
  1242. }
  1243. })
  1244. /** word比较特殊需要精确到note位置 */
  1245. words.forEach((word) => {
  1246. const text = word.textContent || ''
  1247. if (
  1248. (isSpecialMark(text) ||
  1249. isSpeedKeyword(text) ||
  1250. isGradientWords(text) ||
  1251. isRepeatWord(text) ||
  1252. GRADIENT_SPEED_RESET_TAG) &&
  1253. text
  1254. ) {
  1255. const wordContainer = word.parentElement?.parentElement
  1256. const parentMeasure = wordContainer?.parentElement
  1257. const measureWords = [...(parentMeasure?.childNodes || [])]
  1258. const wordIndex = wordContainer ? measureWords.indexOf(wordContainer) : -1
  1259. if (wordContainer && parentMeasure && wordIndex > -1) {
  1260. const index = firstMeasures.indexOf(parentMeasure)
  1261. const activeMeasure = part.getElementsByTagName('measure')[index]
  1262. setElementNoteBefore(wordContainer, parentMeasure, activeMeasure)
  1263. // activeMeasure?.insertBefore(wordContainer.cloneNode(true), activeMeasure?.childNodes[wordIndex])
  1264. }
  1265. }
  1266. })
  1267. rehearsals.forEach((rehearsal) => {
  1268. const container = rehearsal.parentElement?.parentElement
  1269. const parentMeasure = container?.parentElement
  1270. // console.log(rehearsal)
  1271. if (parentMeasure) {
  1272. const index = firstMeasures.indexOf(parentMeasure)
  1273. part.getElementsByTagName('measure')[index]?.appendChild(container.cloneNode(true))
  1274. // console.log(index, parentMeasure, firstMeasures.indexOf(parentMeasure))
  1275. }
  1276. })
  1277. }
  1278. // 最后一个小节的结束线元素不在最后 调整
  1279. if (part && part.getAttribute('id') === id) {
  1280. const barlines = part.getElementsByTagName('barline')
  1281. const lastParent = barlines[barlines.length - 1]?.parentElement
  1282. if (lastParent?.lastElementChild?.tagName !== 'barline') {
  1283. const children = lastParent?.children || []
  1284. for (let el of children) {
  1285. if (el.tagName === 'barline') {
  1286. // 将结束线元素放到最后
  1287. lastParent?.appendChild(el)
  1288. break
  1289. }
  1290. }
  1291. }
  1292. }
  1293. })
  1294. Array.from(partList).forEach((part) => {
  1295. if (part && part.getAttribute('id') !== id) {
  1296. part.parentNode?.removeChild(part)
  1297. }
  1298. })
  1299. // 处理装饰音问题
  1300. const notes = xmlParse.getElementsByTagName('note')
  1301. const getNextvNoteDuration = (i: number) => {
  1302. let nextNote = notes[i + 1]
  1303. // 可能存在多个装饰音问题,取下一个非装饰音时值
  1304. for (let index = i; index < notes.length; index++) {
  1305. const note = notes[index]
  1306. if (!note.getElementsByTagName('grace')?.length) {
  1307. nextNote = note
  1308. break
  1309. }
  1310. }
  1311. const nextNoteDuration = nextNote?.getElementsByTagName('duration')[0]
  1312. return nextNoteDuration
  1313. }
  1314. Array.from(notes).forEach((note, i) => {
  1315. const graces = note.getElementsByTagName('grace')
  1316. if (graces && graces.length) {
  1317. // if (i !== 0) {
  1318. note.appendChild(getNextvNoteDuration(i)?.cloneNode(true))
  1319. // }
  1320. }
  1321. })
  1322. }
  1323. // console.log(new XMLSerializer().serializeToString(xmlParse))
  1324. return new XMLSerializer().serializeToString(xmlParse)
  1325. }
  1326. // 倚音后连音线
  1327. export const appoggianceFormate = (xml: string): string => {
  1328. if (!xml) return xml
  1329. const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
  1330. const graces = xmlParse.querySelectorAll('grace')
  1331. if (!graces.length) return xml
  1332. const getNextElement = (el: HTMLElement): HTMLElement => {
  1333. if (el.querySelector('grace')) {
  1334. return getNextElement(el?.nextElementSibling as HTMLElement)
  1335. }
  1336. return el
  1337. }
  1338. for (let grace of graces) {
  1339. const notations = grace.parentElement?.querySelector('notations')
  1340. if (notations && notations.querySelectorAll('slur').length > 1) {
  1341. let nextEle: Element = getNextElement(grace.parentElement?.nextElementSibling as HTMLElement)
  1342. if (nextEle && nextEle.querySelectorAll('slur').length > 0) {
  1343. const slurNumber = Array.from(nextEle.querySelector('notations')?.children || []).map((el: Element) => {
  1344. return el.getAttribute('number')
  1345. })
  1346. const slurs = notations.querySelectorAll('slur')
  1347. for (let nota of slurs) {
  1348. if (!slurNumber.includes(nota.getAttribute('number'))) {
  1349. nextEle.querySelector('notations')?.appendChild(nota)
  1350. }
  1351. }
  1352. }
  1353. }
  1354. }
  1355. return new XMLSerializer().serializeToString(xmlParse)
  1356. }
  1357. export const getVoicePartInfo = () => {
  1358. const { MusicalInstrumentClassification, chinesePartName } = appState
  1359. let subjectId = -1
  1360. const { partListNames, partIndex } = state
  1361. const filterPartNames = partListNames.filter((item) => (item || '').trim() !== '')
  1362. if (filterPartNames.length) {
  1363. for (const Classification of Object.entries(MusicalInstrumentClassification)) {
  1364. const [key, value] = Classification as [string, string[]]
  1365. const activePart = partListNames[partIndex]
  1366. // console.log({activePart, value, partListNames})
  1367. const filterValue = value.filter((item) => item && activePart.indexOf(item || '') > -1)
  1368. if (activePart && (filterValue.length || value.includes(activePart))) {
  1369. if (!isNaN(+key)) {
  1370. subjectId = +key
  1371. }
  1372. return {
  1373. realPartListNames: partListNames,
  1374. subjectId: subjectId,
  1375. partListNames: value,
  1376. partName: activePart,
  1377. chinesePartName: chinesePartName[activePart] || activePart,
  1378. }
  1379. }
  1380. }
  1381. }
  1382. return {
  1383. subjectId: subjectId,
  1384. partListNames: [],
  1385. }
  1386. }
  1387. /** 根据ID获取最顶级ID */
  1388. export const isWithinScope = (tree: any[], id: number): number => {
  1389. if (!tree) return 0
  1390. let result = 0
  1391. for (const item of tree) {
  1392. if (item.id === id) {
  1393. result = item.id
  1394. break
  1395. }
  1396. if (item.sysMusicScoreCategoriesList) {
  1397. result = isWithinScope(item.sysMusicScoreCategoriesList, id)
  1398. if (result > 0) {
  1399. result = item.id
  1400. }
  1401. if (result) break
  1402. }
  1403. }
  1404. return result
  1405. }
  1406. /**
  1407. * 特殊教材分类id
  1408. */
  1409. export const classids = [1, 30]
  1410. /**
  1411. * 指定id是否在给定的类别中
  1412. * @param tree
  1413. * @param id
  1414. * @param parentInTree default: false
  1415. */
  1416. export const idIsInClassIds = (tree: any[], id: number, parentInTree = false): boolean => {
  1417. if (!tree) return false
  1418. let result = false
  1419. for (const item of tree) {
  1420. // console.log(id, item.id, (parentInTree || classids.includes(item.id)))
  1421. if (item.id === id && (parentInTree || classids.includes(item.id))) {
  1422. result = true
  1423. break
  1424. }
  1425. if (item.sysMusicScoreCategoriesList) {
  1426. result = idIsInClassIds(item.sysMusicScoreCategoriesList, id, parentInTree || classids.includes(item.id))
  1427. if (result) break
  1428. }
  1429. }
  1430. // console.log('result', result)
  1431. return result
  1432. }
  1433. /**
  1434. * 获取图片地址
  1435. * @param src 图片地址
  1436. * @returns
  1437. */
  1438. const getImageSize = (src: string): Promise<HTMLImageElement> => {
  1439. return new Promise((resolve, reject) => {
  1440. const img = new Image()
  1441. img.src = src
  1442. img.onload = () => {
  1443. resolve(img)
  1444. }
  1445. img.onerror = (err) => reject(err)
  1446. })
  1447. }