helpers.ts 57 KB

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