| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586 |
- import state from './state'
- import appState from '/src/state'
- import { browser } from '/src/helpers/utils'
- import runtime, { getFixTime, getFirsrNoteByMeasureListIndex, getBoundingBoxByNote } from './runtime'
- // @ts-ignore
- import {
- isSpecialMark,
- isSpeedKeyword,
- Fraction,
- SourceMeasure,
- isGradientWords,
- GRADIENT_SPEED_RESET_TAG,
- StringUtil,
- } from '/osmd-extended/src'
- import dayjs from 'dayjs'
- const browserInfo = browser()
- const getLinkId = (): string => {
- return location.hash.split('?')[0].split('/').pop() || ''
- }
- export const retain = (time: number) => {
- return Math.ceil(time * 1000000) / 1000000
- }
- export function formatBeatUnit(beatUnit: string) {
- let multiple = 4
- switch (beatUnit) {
- case '1024th':
- // bpm = bpm;
- multiple = 1024
- break
- case '512th':
- // divisionsFromNote = (noteDuration / 4) * 512;
- multiple = 512
- break
- case '256th':
- // divisionsFromNote = (noteDuration / 4) * 256;
- multiple = 256
- break
- case '128th':
- // divisionsFromNote = (noteDuration / 4) * 128;
- multiple = 128
- break
- case '64th':
- // divisionsFromNote = (noteDuration / 4) * 64;
- multiple = 64
- break
- case '32nd':
- // divisionsFromNote = (noteDuration / 4) * 32;
- multiple = 32
- break
- case '16th':
- // divisionsFromNote = (noteDuration / 4) * 16;
- multiple = 16
- break
- case 'eighth':
- // divisionsFromNote = (noteDuration / 4) * 8;
- multiple = 8
- break
- case 'quarter':
- multiple = 4
- break
- case 'half':
- // divisionsFromNote = (noteDuration / 4) * 2;
- multiple = 2
- break
- case 'whole':
- // divisionsFromNote = (noteDuration / 4);
- multiple = 1
- break
- case 'breve':
- // divisionsFromNote = (noteDuration / 4) / 2;
- multiple = 0.5
- break
- case 'long':
- // divisionsFromNote = (noteDuration / 4) / 4;
- multiple = 0.25
- break
- case 'maxima':
- // divisionsFromNote = (noteDuration / 4) / 8;
- multiple = 0.125
- break
- default:
- break
- }
- return multiple
- }
- export const formatLyricsEntries = (note: any) => {
- const voiceEntries = note.parentStaffEntry?.voiceEntries || []
- const lyricsEntries: string[] = []
- for (const voic of voiceEntries) {
- if (voic.lyricsEntries?.table) {
- const values: any[] = Object.values(voic.lyricsEntries.table)
- for (const lyric of values) {
- lyricsEntries.push(lyric?.value.text)
- }
- }
- }
- return lyricsEntries
- }
- export const getMeasureDurationDiff = (measure: any) => {
- const { realValue: SRealValue } = measure.activeTimeSignature
- const { realValue: RRealValue } = measure.duration
- return SRealValue - RRealValue
- }
- export type GradualChange = {
- resetXmlNoteIndex: number
- startXmlNoteIndex: number
- endXmlNoteIndex: number
- startWord: string
- }
- const speedInfo: { [key in string]: number } = {
- 'rall.': 1.333333333,
- 'poco rit.': 1.333333333,
- 'rit.': 1.333333333,
- 'molto rit.': 1.333333333,
- 'molto rall': 1.333333333,
- lentando: 1.333333333,
- allargando: 1.333333333,
- morendo: 1.333333333,
- 'accel.': 0.8,
- calando: 2,
- 'poco accel.': 0.8,
- }
- /** 按照dorico的渐快渐慢生成对应的速度 */
- export const createSpeedInfo = (gradualChange: GradualChange | undefined, speed: number) => {
- if (gradualChange && speedInfo[gradualChange.startWord?.toLocaleLowerCase()]) {
- const notenum = Math.max(gradualChange.endXmlNoteIndex, 3)
- const speeds: number[] = []
- const startSpeed = speed
- const endSpeed = speed / speedInfo[gradualChange.startWord?.toLocaleLowerCase()]
- for (let index = 0; index < notenum; index++) {
- const speed = startSpeed + ((endSpeed - startSpeed) / notenum) * (index + 1)
- speeds.push(speed)
- }
- return speeds
- }
- return
- }
- const tranTime = (str: string = '') => {
- let result = str
- const splits = str.split(':')
- if (splits.length === 1) {
- result = `00:${splits[0]}:00`
- } else if (splits.length === 2) {
- result = `00:${splits[0]}:${splits[1]}`
- }
- // console.log(`1970-01-01 00:${result}0`)
- return `1970-01-01 00:${result}0`
- }
- export const getAllNodes = (osmd: any) => {
- //console.log(9999, osmd)
- const detailId = getLinkId()
- let fixtime = browserInfo.huawei ? 0.08 : 0 //getFixTime()
- const allNotes: any[] = []
- const allNoteId: string[] = []
- const allMeasures: any[] = []
- const { baseSpeed = 100 } = state
- const formatRealKey = (realKey: number, detail: any) => {
- // 长笛的LEVEL 2-5-1条练习是泛音练习,以每小节第一个音的指法为准,高音不变变指法。
- const olnyOneIds = ['906']
- if (olnyOneIds.includes(detailId)) {
- return detail.measures[0]?.realKey || realKey
- }
- // 圆号的LEVEL 2-5条练习是泛音练习,最后四小节指法以连音线第一个小节为准
- const olnyOneIds2 = ['782', '784']
- if (olnyOneIds2.includes(detailId)) {
- const measureNumbers = [14, 16, 30, 32]
- if (measureNumbers.includes(detail.firstVerticalMeasure?.measureNumber)) {
- return allNotes[allNotes.length - 1]?.realKey || realKey
- }
- }
- // 2-6 第三小节指法按照第一个音符显示
- const filterIds = ['900', '901', '640', '641', '739', '740', '800', '801', '773', '774', '869', '872', '714', '715']
- if (filterIds.includes(detailId)) {
- if (detail.firstVerticalMeasure?.measureNumber === 3 || detail.firstVerticalMeasure?.measureNumber === 9) {
- return detail.measures[0]?.realKey || realKey
- }
- }
- return realKey
- }
- if (state.gradualTimes) {
- console.log('合奏速度', state.gradual, state.gradualTimes)
- }
- if (osmd?.cursor) {
- try {
- osmd.cursor.reset()
- } catch (error) {}
- const iterator = osmd.cursor.iterator
- let i = 0
- let si = 0
- let measures: any[] = []
- let stepSpeeds: number[] = []
- let usetime = 0
- let relaMeasureLength = 0
- /** 弱起时间 */
- let difftime = 0
- let beatUnit = 'quarter'
- let useGradualTime = 0
- let gradualSpeed
- let gradualChange: GradualChange | undefined
- let gradualChangeIndex = 0
- let measureNumberPrinted = 1
- let indexOf = 0
- let staveIndex = 0
- let staveNoteIndex = 0
- let currentRealValueTotal = 0
- let skipNextNote = false
- let multipleRestMeasures = 0
- // const useedmeasures: Set<number> = new Set()
- while (!iterator.endReached) {
- // 为获取所有节点有修改源码currentVoiceEntries仅返回可见第一行,请搜索“修改为 仅根据当前可见声部第一行跳转”可找到位置
- // 多声部仅循环一次,以第一声部为准
- // console.log('Entries', {...osmd.cursor}, osmd.cursor.cursorElement)
- const cursorImg: HTMLElement = osmd.cursor.cursorElement
- const cursorBox: any = { move: false }
- if (cursorImg) {
- cursorBox.move = true
- cursorBox.x = cursorImg.offsetLeft
- cursorBox.y = cursorImg.offsetTop
- cursorBox.w = cursorImg.offsetWidth
- cursorBox.h = cursorImg.offsetHeight
- }
- const voiceEntries = iterator.currentVoiceEntries?.[0] ? [iterator.currentVoiceEntries?.[0]] : []
- const voiceEntries2 = iterator.currentVoiceEntries?.[1]
- let skipMode = false
- for (const v of voiceEntries) {
- // 始终只取第一个声部中第一个音符的时间
- let note: any = v.notes[0]
- // console.log( note.sourceMeasure.verticalMeasureList)
- if (['Piano'].includes(state.activeDetail?.code) || state.activeDetail?.musicSheetType == 'CONCERT') {
- let _notes = []
- try {
- _notes = iterator.currentVoiceEntries
- ?.map((_n: any) => _n.notes)
- .flat()
- .sort((a: any, b: any) => a.Length.realValue - b.Length.realValue)
- note = _notes[0]
- } catch (error) {}
- }
- if (note) {
- if (si === 0) {
- allMeasures.push(note.sourceMeasure)
- }
- if (si === 0 && state.isSpecialBookCategory) {
- for (const expression of note.sourceMeasure?.TempoExpressions) {
- if (expression?.InstantaneousTempo?.beatUnit) {
- // 取最后一个有效的tempo
- beatUnit = expression.InstantaneousTempo.beatUnit
- }
- }
- }
- measureNumberPrinted = note.sourceMeasure?.MeasureNumberXML
- // 判断是否是同一小节
- if (staveIndex == note.sourceMeasure?.MeasureNumberXML) {
- staveNoteIndex++
- } else {
- // staveIndex不同,重新赋值
- staveIndex = note.sourceMeasure?.MeasureNumberXML
- staveNoteIndex = 0
- }
- let measureSpeed = note.sourceMeasure.tempoInBPM
- const { metronomeNoteIndex } = iterator.currentMeasure
- if (metronomeNoteIndex !== 0 && metronomeNoteIndex > si) {
- measureSpeed = allNotes[allNotes.length - 1]?.speed || 100
- }
- const activeVerticalMeasureList = [note.sourceMeasure.verticalMeasureList?.[0]] || []
- const { realValue } = iterator.currentTimeStamp
- const { RealValue: vRealValue, Denominator: vDenominator } = formatDuration(
- iterator.currentMeasure.activeTimeSignature,
- iterator.currentMeasure.duration
- )
- let { wholeValue, numerator, denominator, realValue: noteRealValue } = note.length
- let relativeTime = usetime //realValue * 4 * (60 / measureSpeed)
- /**
- * 速度不能为0, 此处的速度应该是按照设置的速度而不是校准后的速度,否则mp3速度不对
- */
- // let beatSpeed =
- // (state.isSpecialBookCategory
- // ? getTimeByBeatUnit(beatUnit, measureSpeed, iterator.currentMeasure.activeTimeSignature.Denominator)
- // : baseSpeed) || 1
- // #8456 光标和节拍对应不上等bug修复,
- let beatSpeed = (state.isSpecialBookCategory ? measureSpeed : baseSpeed) || 1;
- let speed = (state.isSpecialBookCategory ? measureSpeed : baseSpeed) || 1
- // console.log('曲谱设置的速度', { base: getTimeByBeatUnit(beatUnit, measureSpeed, iterator.currentMeasure.activeTimeSignature.Denominator), beatSpeed, speed})
- gradualChange = iterator.currentMeasure.speedInfo || gradualChange
- gradualSpeed = osmd.sheet.soundTempos?.get(note.sourceMeasure.measureListIndex) || gradualSpeed
- if (!gradualSpeed || gradualSpeed.length < 2) {
- gradualSpeed = createSpeedInfo(gradualChange, speed)
- }
- /**
- * 渐变速度逻辑如下:
- * 1. 获取渐变关键词,记录开始区间、结束区间、渐变整体结束;
- * 2. 计算区间内每个小节最小音符时长单位,整除计算比例;
- * 3. 按照比例计算下一个音符的速度;
- * 4. 合奏曲目这里需要处理一下,因为音符位置可能不太相同,理想的情况是每个分谱指定结束时间.
- */
- let gradualLength = 0
- const measureListIndex = iterator.currentMeasure.measureListIndex
- if (state.gradualTimes && Object.keys(state.gradualTimes).length > 0) {
- const withInRangeNote = state.gradual.find((item, index) => {
- const nextItem: any = state.gradual[index + 1]
- return (
- item[0].measureIndex <= measureListIndex &&
- item[1]?.measureIndex! >= measureListIndex &&
- (!nextItem || nextItem?.[0].measureIndex !== measureListIndex)
- )
- })
- if (!withInRangeNote) {
- useGradualTime = 0
- }
- const [first, last] = withInRangeNote || []
- if (first && last) {
- // 小节数量
- const continuous = last.measureIndex - first.measureIndex
- // 开始小节内
- const inTheFirstMeasure = first.closedMeasureIndex == measureListIndex && si >= first.noteInMeasureIndex
- // 结束小节内
- const inTheLastMeasure = last.closedMeasureIndex === measureListIndex && si < last.noteInMeasureIndex
- // 范围内小节
- const inFiestOrLastMeasure =
- first.closedMeasureIndex !== measureListIndex && last.closedMeasureIndex !== measureListIndex
- if (inTheFirstMeasure || inTheLastMeasure || inFiestOrLastMeasure) {
- const startTime = state.gradualTimes[first.measureIndex]
- const endTime = state.gradualTimes[last.measureIndex]
- if (startTime && endTime) {
- const times =
- continuous - first.leftDuration / first.allDuration + last.leftDuration / last.allDuration
- const diff = dayjs(tranTime(endTime)).diff(dayjs(tranTime(startTime)), 'millisecond')
- gradualLength = ((noteRealValue / vRealValue / times) * diff) / 1000
- useGradualTime += gradualLength
- }
- }
- }
- } else if (
- gradualChange &&
- gradualSpeed &&
- (gradualChange.startXmlNoteIndex === si || gradualChangeIndex > 0)
- ) {
- const startSpeed = gradualSpeed[0] - (gradualSpeed[1] - gradualSpeed[0])
- const { resetXmlNoteIndex, endXmlNoteIndex } = gradualChange
- const noteDiff = endXmlNoteIndex
- let stepSpeed = (gradualSpeed[gradualSpeed.length - 1] - startSpeed) / noteDiff
- stepSpeed = note.DotsXml ? stepSpeed / 1.5 : stepSpeed
- if (gradualChangeIndex < noteDiff) {
- const tempSpeed = Math.ceil(speed + stepSpeed * gradualChangeIndex)
- let tmpSpeed = getTimeByBeatUnit(
- beatUnit,
- tempSpeed,
- iterator.currentMeasure.activeTimeSignature.Denominator
- )
- const maxLength = (wholeValue + numerator / denominator) * vDenominator * (60 / tmpSpeed)
- speed += Math.ceil(stepSpeed * (gradualChangeIndex + 1))
- tmpSpeed = getTimeByBeatUnit(beatUnit, speed, iterator.currentMeasure.activeTimeSignature.Denominator)
- const minLength = (wholeValue + numerator / denominator) * vDenominator * (60 / tmpSpeed)
- gradualLength = (maxLength + minLength) / 2
- } else if (resetXmlNoteIndex > gradualChangeIndex) {
- speed = allNotes[i - 1]?.speed
- }
- beatSpeed =
- (state.isSpecialBookCategory
- ? getTimeByBeatUnit(beatUnit, speed, iterator.currentMeasure.activeTimeSignature.Denominator)
- : baseSpeed) || 1
- const isEnd = !(gradualChangeIndex < noteDiff) && !(resetXmlNoteIndex > gradualChangeIndex)
- gradualChangeIndex++
- // console.log(gradualChangeIndex)
- if (isEnd) {
- gradualChangeIndex = 0
- gradualChange = undefined
- gradualSpeed = undefined
- stepSpeeds = []
- }
- }
- if (i === 0) {
- fixtime += getFixTime(beatSpeed)
- }
- // console.log(fixtime, '看看1',beatSpeed, baseSpeed,beatUnit, speed, iterator.currentMeasure.activeTimeSignature.Denominator)
- // 酷乐秀计算音符时值方法
- // let noteLength =
- // (numerator === 0 && note.isRestFlag ? vRealValue : (wholeValue + numerator) / denominator) *
- // vDenominator *
- // (60 / beatSpeed)
- //管乐迷计算时值方法
- let noteLength = gradualLength
- ? gradualLength
- : Math.min(vRealValue, noteRealValue) * formatBeatUnit(beatUnit) * (60 / beatSpeed)
- const measureLength = vRealValue * vDenominator * (60 / beatSpeed)
- // 单独处理个别的声部
- if (['Piano'].includes(state.activeDetail?.code)) {
- const currentRealValue = iterator.currentTimeStamp.realValue - currentRealValueTotal
- noteLength =
- (currentRealValue || (numerator === 0 ? vRealValue : (wholeValue + numerator) / denominator)) *
- vDenominator *
- (60 / beatSpeed)
- }
- /**
- *
- * bug修复说明
- * 曲目:'我和我的祖国’,
- * 从第32节点后播放异常,进和管乐迷endtime值对比,发现是noteLength计算不一致导致
- * 不一致的原因是:noteLength值计算,多了下面这段逻辑,故先注释掉
- * 20.23.10.13
- *
- */
- // 如果是休止符并且整个小节休止,休止符的时值小于小节时值,取小节的时值
- // if (note.isRestFlag && note?.sourceMeasure?.allRests) {
- // multipleRestMeasures = note?.sourceMeasure?.multipleRestMeasures
- // }
- // if (multipleRestMeasures > 0) {
- // multipleRestMeasures -= 1
- // noteLength = measureLength
- // }
- // 如果休止符的时值大于小节的时值
- if (note.isRestFlag && noteLength > measureLength) {
- noteLength = measureLength
- }
- // 处理附点时长不正确问题
- if (note.DotsXml && note.tuplet) {
- console.log('处理附点时长不正确问题')
- noteLength = noteLength * 1.5
- }
- // 后倚音通过跳过的方式实现
- if (skipNextNote) {
- noteLength = 0.0000001
- skipNextNote = false
- skipMode = true
- }
- const Expressions = note.sourceMeasure.staffLinkedExpressions?.[0]
- for (const Expression of Expressions || []) {
- if (Expression) {
- const needSkip = Expression.expressions?.find((item: any) => item.label === '跳过下一个')
- if (needSkip && Fraction.Equal(note.voiceEntry?.Timestamp, Expression.Timestamp)) {
- skipNextNote = true
- break
- }
- }
- }
- currentRealValueTotal = iterator.currentTimeStamp.realValue
- usetime += noteLength
- relaMeasureLength += noteLength
- //console.log('👀看看noteLength',noteLength,relativeTime, i)
- let relaEndtime = noteLength + relativeTime
- // console.log({noteLength,relativeTime ,relaEndtime, endtime: relaEndtime + fixtime})
- const fixedKey = note.ParentVoiceEntry.ParentVoice.Parent.SubInstruments[0].fixedKey || 0
- // const svgElelent = activeVerticalMeasureList[0]?.vfVoices['1']?.tickables[si]
- const svgElelent = activeVerticalMeasureList[0]?.vfVoices['1']?.tickables[staveNoteIndex]
- // console.log(relativeTime)
- if (allNotes.length && allNotes[allNotes.length - 1].relativeTime === relativeTime) {
- continue
- }
- // console.log(iterator.currentMeasure)
- // 如果是弱起就补齐缺省的时长
- if (i === 0) {
- const diff = getMeasureDurationDiff(iterator.currentMeasure);
- let _firstMeasureRealValue = 0
- const staffEntries = note.sourceMeasure.verticalMeasureList?.[0]?.staffEntries || []
- //计算第一个小节里面的音符时值是否等于整个小节的时值
- staffEntries.forEach((_a: any) => {
- if(_a?.sourceStaffEntry?.voiceEntries?.[0]?.notes?.[0]?.length?.realValue){
- _firstMeasureRealValue += _a.sourceStaffEntry.voiceEntries[0].notes[0].length.realValue
- }
- })
- // if (_firstMeasureRealValue < vRealValue){
- // // console.log(_firstMeasureRealValue, vRealValue)
- // // 如果是弱起,将整个小节的时值减去音符的时值,就是缺省的时值
- // difftime = measureLength - noteLength
- // }
- if (diff > 0) {
- difftime = diff * formatBeatUnit(beatUnit) * (60 / beatSpeed);
- }
- /** 如果是酷乐秀MIDI类型文件就不处理弱起 */
- if (state.activeDetail?.audioType === 'MIDI') {
- difftime = 0
- }
- fixtime += difftime
- }
- // console.log('👀看看endtime', relaEndtime, fixtime, i)
- const _noteLength = noteRealValue
- const nodeDetail = {
- _noteLength,
- fixtime,
- skipMode,
- NoteToGraphicalNoteObjectId: note.NoteToGraphicalNoteObjectId,
- cursorBox,
- skipNextNote,
- measureNumberPrinted,
- difftime,
- octaveOffset: activeVerticalMeasureList[0]?.octaveOffset,
- frequency: note.pitch?.frequency,
- speed,
- beatSpeed,
- i,
- si,
- stepSpeeds,
- indexOfMeasures: indexOf,
- measureOpenIndex: allMeasures.length - 1,
- measures, //: groupMeasures[indexOf],
- tempoInBPM: note.sourceMeasure.tempoInBPM,
- measureLength,
- relaMeasureLength,
- id: svgElelent?.attrs.id,
- note: note.halfTone + 12, // see issue #224
- relativeTime: retain(relativeTime),
- time: retain(relativeTime + fixtime),
- endtime: retain(relaEndtime + fixtime),
- relaEndtime: retain(relaEndtime),
- realValue,
- halfTone: note.halfTone,
- voiceEntry: {
- isStaccato: note.voiceEntry.isStaccato(),
- Timestamp: { ...note.voiceEntry.Timestamp },
- ornamentContainer: note.voiceEntry.ornamentContainer ? { ...note.voiceEntry.ornamentContainer } : '',
- },
- noteElement: {
- ...note.SourceMeasure,
- NoteToGraphicalNoteObjectId: note.NoteToGraphicalNoteObjectId,
- notehead: note.notehead
- ? {
- filled: note.notehead.filled,
- shape: note.notehead.shape,
- sourceNote: note.notehead.sourceNote?.NoteToGraphicalNoteObjectId,
- }
- : '',
- noteheadColor: note.noteheadColor,
- isRestFlag: note.isRestFlag,
- sourceMeasure: {
- ...note.SourceMeasure,
- measureListIndex: note?.SourceMeasure?.measureListIndex,
- MeasureNumberXML: note?.SourceMeasure?.MeasureNumberXML,
- allRests: note?.SourceMeasure?.allRests,
- isRestFlag: note?.SourceMeasure?.isRestFlag,
- multipleRestMeasures: note?.SourceMeasure?.multipleRestMeasures,
- verticalMeasureList: Array.isArray(note?.SourceMeasure?.verticalMeasureList)
- ? note.SourceMeasure.verticalMeasureList.map((v: any) => {
- const { x, y, width, height, start_x, end_x } = v?.stave || {}
- return v
- ? {
- stave: {
- x,
- y,
- width,
- height,
- start_x,
- end_x,
- },
- boundingBox:
- v && v.boundingBox
- ? {
- absolutePosition: { ...v.boundingBox.absolutePosition },
- size: { ...v.boundingBox.size },
- }
- : '',
- }
- : undefined
- })
- : [],
- activeTimeSignature: note.sourceMeasure?.activeTimeSignature,
- ActiveTimeSignature: note.sourceMeasure?.activeTimeSignature
- },
- tie: note.tie
- ? {
- StartNote: {
- NoteToGraphicalNoteObjectId: note.tie.StartNote.NoteToGraphicalNoteObjectId,
- },
- notes:
- (Array.isArray(note.tie.notes) &&
- note.tie.notes?.map((_tie: any) => {
- return {
- NoteToGraphicalNoteObjectId: _tie.NoteToGraphicalNoteObjectId,
- }
- })) ||
- [],
- }
- : '',
- slurs: Array.isArray(note.slurs)
- ? note.slurs.map((slur: any) => {
- return {
- startNote: {
- NoteToGraphicalNoteObjectId: slur.startNote.NoteToGraphicalNoteObjectId,
- },
- endNote: {
- NoteToGraphicalNoteObjectId: slur.endNote.NoteToGraphicalNoteObjectId,
- },
- }
- })
- : [],
- pitch: {
- prevFrequency: note.pitch?.prevFrequency,
- nextFrequency: note.pitch?.nextFrequency,
- frequency: note.pitch?.frequency,
- },
- Length: {
- ...note.Length,
- },
- },
- svgElelent: svgElelent || '',
- fixedKey,
- realKey: 0,
- duration: 0,
- formatLyricsEntries: formatLyricsEntries(note),
- stave:
- activeVerticalMeasureList[0] && activeVerticalMeasureList[0].stave
- ? {
- attrs: activeVerticalMeasureList[0].stave ? { ...activeVerticalMeasureList[0].stave.attrs } : {},
- }
- : '',
- firstVerticalMeasure: { measureNumber: activeVerticalMeasureList?.[0]?.measureNumber },
- noteLength: 1,
- halfTone1: Array.isArray(v.notes) ? v.notes.map((n: any) => n.halfTone + 12).filter(Boolean) : [],
- halfTone2:
- voiceEntries2 && Array.isArray(voiceEntries2.notes)
- ? voiceEntries2.notes.map((n: any) => n.halfTone + 12).filter(Boolean)
- : [],
- }
- nodeDetail.realKey = formatRealKey(note.halfTone - fixedKey * 12, nodeDetail)
- nodeDetail.duration = nodeDetail.endtime - nodeDetail.time
- const tickables = activeVerticalMeasureList[0]?.vfVoices['1']?.tickables || []
- const sublength = note.sourceMeasure.verticalMeasureList?.[0]?.staffEntries?.length || tickables.length
- nodeDetail.noteLength = sublength || 1
- allNotes.push(nodeDetail)
- allNoteId.push(nodeDetail.id)
- measures.push(nodeDetail)
- if (si < sublength - 1) {
- si++
- } else {
- si = 0
- relaMeasureLength = 0
- measures = []
- }
- }
- }
- // iterator.moveToNextVisibleVoiceEntry(false)
- osmd.cursor.next()
- i++
- }
- try {
- osmd.cursor.reset()
- } catch (error) {}
- }
- // 按照时间轴排序
- // console.log('看看👀', allNotes)
- const sortArray = allNotes
- .sort((a, b) => a.relativeTime - b.relativeTime)
- .map((item, index) => ({ ...item, i: index }))
- // for (let i = 0; i < sortArray.length; i++) {
- // const note = { ...sortArray[i] }
- // const prevNote = sortArray[i - 1]
- // const isNotNeedStop = note.noteElement.tie && prevNote?.noteElement.tie && note.halfTone === prevNote?.halfTone
- // const isOvertone = false
- // if (prevNote) {
- // if (isNotNeedStop || isOvertone) {
- // note.sourceStartTime = note.time
- // note.sourceRelativeTime = note.relativeTime
- // note.sourceRealValue = note.realValue
- // note.sourceEndTime = note.endtime
- // note.sourceRelaEndtime = note.relaEndtime
- // note.relativeTime = prevNote.relativeTime
- // note.realValue = prevNote.realValue
- // note.time = prevNote.time
- // note.endtime = prevNote.endtime
- // note.relaEndtime = prevNote.relaEndtime
- // }
- // // 此处会导致休止符继续上一个音的指法
- // if (note.halfTone === 0) {
- // note.realKey = prevNote.realKey
- // }
- // }
- // sortArray[i] = note
- // }
- // console.log(sortArray)
- return sortArray
- }
- export const getAllNoteElements = (osmd: any) => {
- const list: any[] = []
- const listById: {
- [key: string]: any
- } = {}
- for (const measure of osmd.drawer.graphicalMusicSheet.measureList) {
- const activeMeasure = measure[0]
- for (const tickable of activeMeasure.vfVoices['1'].tickables) {
- list.push(tickable)
- listById[tickable.attrs.id] = tickable
- }
- }
- return {
- list,
- listById,
- }
- }
- export const setStepIndex = (osmd: any, num: number, prev?: number) => {
- // console.log('进度',num,prev)
- if (osmd.product) {
- if (num || num === 0) {
- // console.log(prev, num)
- if (prev && num - prev === 1) {
- osmd.cursor.setPosition({ ...state.times[num].cursorBox })
- } else if (prev && num - prev > 0) {
- while (num - prev > 0) {
- prev++
- osmd.cursor.setPosition({ ...state.times[prev].cursorBox })
- }
- } else {
- osmd.cursor.setPosition({ ...state.times[num].cursorBox })
- }
- }
- } else {
- if (num || num === 0) {
- // console.log(prev, num)
- if (prev && num - prev === 1) {
- osmd.cursor.next()
- } else if (prev && num - prev > 0) {
- while (num - prev > 0) {
- prev++
- num - prev > 0
- osmd.cursor.next()
- }
- } else {
- let i = 0
- osmd.cursor.reset()
- while (i < num) {
- i++
- if (osmd.cursor.hidden !== false) {
- osmd.cursor.show()
- } else {
- // console.log(i, num)
- osmd.cursor.next()
- }
- }
- }
- }
- }
- }
- export const getIndex = (times: any[], currentTime: Number) => {
- // console.log(currentTime)
- if (currentTime > state.times[state.times.length - 1].endtime) {
- return state.times.length - 1;
- }
- let index = 0;
- for (let i = 0; i < times.length; i++) {
- const item = times[i];
- const prevItem = times[i - 1];
- if (currentTime >= item.time) {
- if (!prevItem || item.time != prevItem.time) {
- index = item.i;
- }
- } else {
- break;
- }
- }
- if (state.sectionStatus && state.section.length === 2) {
- // 限制不超过此范围
- const startSection = state.befireSection || state.section[0];
- index = Math.min(Math.max(index, startSection.i), state.section[1].i);
- // console.log('endIndex', index)
- }
- return index;
- };
- export const getSlursNote = (_note: any, pos?: 'start' | 'end') => {
- const note: any =
- state.times.find((n: any) => n.NoteToGraphicalNoteObjectId == _note.NoteToGraphicalNoteObjectId) || {}
- let itemNote = pos === 'end' ? note.noteElement.slurs[0]?.endNote : note.noteElement.slurs[0]?.startNote
- // console.log("🚀 ~ itemNote", itemNote, note)
- if (!itemNote) return undefined
- return state.times.find((n: any) => n.NoteToGraphicalNoteObjectId == itemNote.NoteToGraphicalNoteObjectId)
- }
- export const getNoteBySlursStart = (note: any, anyNoteHasSlurs?: boolean, pos?: 'start' | 'end') => {
- let activeNote = note
- let slursNote = getSlursNote(activeNote, pos)
- if (!slursNote && anyNoteHasSlurs) {
- for (const _item of activeNote.measures) {
- const item = state.times.find((n: any) => n.NoteToGraphicalNoteObjectId == _item.NoteToGraphicalNoteObjectId)
- // console.log("🚀 ~ item", item)
- if (item.noteElement.slurs.length) {
- slursNote = getSlursNote(item, pos)
- activeNote = item
- }
- }
- }
- if (activeNote && slursNote !== activeNote.noteElement) {
- // time = activeNote.time
- for (const note of state.times) {
- if (slursNote === note.noteElement) {
- return note
- }
- }
- }
- return activeNote
- }
- /** 根据 noteElement 获取note */
- export const getParentNote = (note: any) => {
- let parentNote
- if (note) {
- // time = activeNote.time
- for (const n of state.times) {
- if (note.NoteToGraphicalNoteObjectId === n.noteElement.NoteToGraphicalNoteObjectId) {
- // console.log(note)
- return n
- }
- }
- }
- return parentNote
- }
- /** 获取小节之间的连音线,仅同音高*/
- export const getNoteByMeasuresSlursStart = (note: any) => {
- let activeNote = note
- let tieNote
- // console.log(note.noteElement)
- if (note.noteElement.tie && note.noteElement.tie.StartNote) {
- tieNote = note.noteElement.tie.StartNote
- }
- if (activeNote && tieNote && tieNote !== activeNote.noteElement) {
- // time = activeNote.time
- for (const note of state.times) {
- if (tieNote.NoteToGraphicalNoteObjectId === note.noteElement.NoteToGraphicalNoteObjectId) {
- // console.log(note)
- return note
- }
- }
- }
- return activeNote
- }
- export const getActtiveNoteByTimes = (evt: MouseEvent) => {
- const el = (evt.target as HTMLDivElement)?.dataset;
- // console.log(state)
- const data: any = {};
- for (const time of state.times) {
- if (time.id && !data[time.id]) {
- data[time.id] = time;
- }
- }
- const activeNote = data[el.id || ""];
- // state.timesById = data
- return activeNote;
- };
- const getPrevHasSourceNote = (note: any) => {
- const indexOf = Math.max(state.times.indexOf(note) - 1, 0)
- for (let index = indexOf; index >= 0; index--) {
- const item = state.times[index]
- if (item?.stave) {
- return item
- }
- }
- }
- export const getBoundingBoxByverticalNote = (note: any) => {
- let measures = note?.noteElement?.sourceMeasure?.verticalMeasureList
- measures =
- !measures || !measures[0]
- ? note?.noteElement?.isRestFlag && getPrevHasSourceNote(note)?.noteElement?.sourceMeasure?.verticalMeasureList
- : measures
- let height = 0
- if (measures) {
- const firstMeasure = measures[runtime.partIndex]
- for (let index = 0; index < measures.length; index++) {
- const measure = measures[index]
- if (measure?.stave) {
- const { height: measureHeight } = measure?.stave
- if (index > 0) {
- height += measures[index - 1]?.stave.height
- }
- height += measureHeight
- const { x, y, width, context, start_x, end_x } = firstMeasure?.stave
- return {
- measureIndex: note?.noteElement?.sourceMeasure.measureListIndex || 0,
- MeasureNumberXML: note?.noteElement?.sourceMeasure.MeasureNumberXML || 1,
- start_x,
- end_x,
- height,
- x,
- y,
- width,
- context,
- }
- }
- }
- }
- return {
- measureIndex: 0,
- height,
- start_x: 0,
- end_x: 0,
- x: 0,
- y: 0,
- width: 0,
- context: {
- element: null,
- },
- }
- }
- export const getDuration = (osmd: any): any => {
- if (osmd) {
- const firstMeasure = osmd?.graphic?.measureList[0][0]
- // console.log(osmd?.graphic?.measureList[0][0]?.parentSourceMeasure)
- if (firstMeasure) {
- const { duration, tempoInBPM, activeTimeSignature, TempoExpressions } = firstMeasure?.parentSourceMeasure
- if (duration) {
- let beatUnit = 'quarter'
- for (const item of TempoExpressions) {
- beatUnit = item.InstantaneousTempo.beatUnit || 'quarter'
- }
- return {
- ...formatDuration(activeTimeSignature, duration),
- tempoInBPM,
- beatUnit,
- }
- }
- }
- }
- return {}
- }
- export const formatDuration = (activeTimeSignature: Fraction, duration: Fraction): Fraction => {
- // 弱起第一小节duration不对
- return activeTimeSignature
- }
- /** 根据音符单位,速度,几几拍计算正确的时间 */
- export const getTimeByBeatUnit = (beatUnit: string, bpm: number, denominator: number) => {
- let multiple = 4
- switch (beatUnit) {
- case '1024th':
- // bpm = bpm;
- multiple = 1024
- break
- case '512th':
- // divisionsFromNote = (noteDuration / 4) * 512;
- multiple = 512
- break
- case '256th':
- // divisionsFromNote = (noteDuration / 4) * 256;
- multiple = 256
- break
- case '128th':
- // divisionsFromNote = (noteDuration / 4) * 128;
- multiple = 128
- break
- case '64th':
- // divisionsFromNote = (noteDuration / 4) * 64;
- multiple = 64
- break
- case '32nd':
- // divisionsFromNote = (noteDuration / 4) * 32;
- multiple = 32
- break
- case '16th':
- // divisionsFromNote = (noteDuration / 4) * 16;
- multiple = 16
- break
- case 'eighth':
- // divisionsFromNote = (noteDuration / 4) * 8;
- multiple = 8
- break
- case 'quarter':
- multiple = 4
- break
- case 'half':
- // divisionsFromNote = (noteDuration / 4) * 2;
- multiple = 2
- break
- case 'whole':
- // divisionsFromNote = (noteDuration / 4);
- multiple = 1
- break
- case 'breve':
- // divisionsFromNote = (noteDuration / 4) / 2;
- multiple = 0.5
- break
- case 'long':
- // divisionsFromNote = (noteDuration / 4) / 4;
- multiple = 0.25
- break
- case 'maxima':
- // divisionsFromNote = (noteDuration / 4) / 8;
- multiple = 0.125
- break
- default:
- break
- }
- return (denominator / multiple) * bpm
- }
- /** 获取第一个小节的节拍速度,替换初始的速度 */
- export const getFirstBpmByMeasures = (measures: SourceMeasure[]) => {
- for (const item of measures) {
- if (item.TempoInBPM || (item as any).tempoInBPM) {
- return getMeasureRealBpm(item)
- }
- }
- return 90
- }
- /**
- * 根据小节获取当前节拍下真实的速度
- * 如: 6/8拍 4分音符bpm速度为120 则计算出8分音符的速度为120 * 2
- */
- export const getMeasureRealBpm = (measure: SourceMeasure) => {
- let bpm = measure.TempoInBPM
- let tempoExpression = null
- let activeTimeSignature: Fraction | null = null
- for (const expression of measure?.TempoExpressions) {
- if (expression?.InstantaneousTempo.beatUnit) {
- // 取最后一个有效的tempo
- tempoExpression = expression
- activeTimeSignature = measure.ActiveTimeSignature
- }
- }
- if (tempoExpression && activeTimeSignature) {
- bpm = tempoExpression?.InstantaneousTempo.TempoInBpm
- // let multiple = 1
- // console.log(tempoExpression.InstantaneousTempo.beatUnit)
- bpm = getTimeByBeatUnit(tempoExpression.InstantaneousTempo.beatUnit, bpm, activeTimeSignature.Denominator)
- }
- return bpm
- }
- export const getEnvHostname = () => {
- if (location.origin.indexOf('online') > -1) {
- return 'https://mstuonline.dayaedu.com'
- } else if (location.origin.indexOf('dev') > -1) {
- return 'http://mstudev.dayaedu.com'
- }
- return 'https://mstutest.dayaedu.com'
- }
- export const getTvIconUrl = () => {
- if (location.origin.indexOf('online') > -1) {
- return 'https://mteaonline.dayaedu.com/#/guide'
- } else if (location.origin.indexOf('dev') > -1) {
- return 'http://mteadev.dayaedu.com/#/guide'
- }
- return 'https://mteatest.dayaedu.com/#/guide'
- }
- export const setPrefix = (url: string): string => {
- if (url) {
- return '?' + url
- }
- return ''
- }
- export type InitXmlInfo = {
- title?: string
- }
- export const formatXML = (xml: string, initInfo?: InitXmlInfo): string => {
- if (!xml) return ''
- const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
- const measures = xmlParse.getElementsByTagName('measure')
- let beats = -1
- let beatType = -1
- /** 创建默认速度标记,避免无速度导致问题 */
- // const defaultSpeedTag = document.createElement('direction')
- // const defaultSpeedTagString = `<direction-type>
- // <metronome default-y="30" color="#000000" font-family="Opus Text Std" font-style="normal" font-size="2.0153" font-weight="normal">
- // <beat-unit>quarter</beat-unit>
- // <per-minute>100</per-minute>
- // </metronome>
- // </direction-type>
- // <voice>1</voice>
- // <staff>1</staff>`
- // defaultSpeedTag.innerHTML = defaultSpeedTagString
- // if (xmlParse.getElementsByTagName('per-minute').length === 0) {
- // measures[0]?.insertAdjacentElement('afterbegin', defaultSpeedTag)
- // }
- // 小节中如果没有节点默认为休止符
- for (const measure of measures) {
- if (beats === -1 && measure.getElementsByTagName('beats').length) {
- beats = parseInt(measure.getElementsByTagName('beats')[0].textContent || '4')
- }
- if (beatType === -1 && measure.getElementsByTagName('beat-type').length) {
- beatType = parseInt(measure.getElementsByTagName('beat-type')[0].textContent || '4')
- }
- // if (speed === -1 && measure.getElementsByTagName('per-minute').length) {
- // speed = parseInt(measure.getElementsByTagName('per-minute')[0].textContent || this.firstLib?.speed)
- // }
- const divisions = parseInt(measure.getElementsByTagName('divisions')[0]?.textContent || '256')
- if (measure.getElementsByTagName('note').length === 0) {
- const forwardTimeElement = measure.getElementsByTagName('forward')[0]?.getElementsByTagName('duration')[0]
- if (forwardTimeElement) {
- forwardTimeElement.textContent = '0'
- }
- measure.innerHTML =
- measure.innerHTML +
- `
- <note>
- <rest measure="yes"/>
- <duration>${divisions * beats}</duration>
- <voice>1</voice>
- <type>whole</type>
- </note>`
- }
- }
- if (initInfo) {
- const workTitle = xmlParse.querySelector('work-title')
- if (workTitle && initInfo.title) {
- workTitle.textContent = initInfo.title
- }
- }
- return new XMLSerializer().serializeToString(xmlParse)
- }
- export type CustomInfo = {
- showSpeed: boolean
- parsedXML: string
- code: string
- }
- /** 从xml中获取自定义信息,并删除多余的字符串 */
- export const getCustomInfo = (xml: string): CustomInfo => {
- const data = {
- showSpeed: true,
- parsedXML: xml,
- code: '',
- }
- const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
- const words = xmlParse.getElementsByTagName('words')
- for (const word of words) {
- if (word && word.textContent?.trim() === '隐藏速度') {
- data.showSpeed = false
- word.textContent = ''
- }
- if (word && word.textContent?.trim() === '@') {
- word.textContent = 'segno'
- }
- }
- data.parsedXML = new XMLSerializer().serializeToString(xmlParse)
- data.code = xmlParse.querySelector('part-name')?.innerHTML || ''
- return data
- }
- /**
- * 替换文本标签中的内容
- */
- const replaceTextConent = (beforeText: string, afterText: string, ele: Element): Element => {
- const words = ele?.getElementsByTagName('words')
- for (const word of words) {
- if (word && word.textContent?.trim() === beforeText) {
- word.textContent = afterText
- }
- }
- return ele
- }
- /**
- * 添加第一分谱信息至当前分谱
- * @param ele 需要插入的元素
- * @param fitstParent 合奏谱第一个分谱
- * @param parent 需要添加的分谱
- */
- const setElementNoteBefore = (ele: Element, fitstParent: Element, parent?: Element | null) => {
- let noteIndex: number = 0
- if (!fitstParent) {
- return
- }
- for (let index = 0; index < fitstParent.childNodes.length; index++) {
- const element = fitstParent.childNodes[index]
- if (element.nodeName === 'note') {
- noteIndex++
- }
- if (element === ele) {
- break
- }
- }
- if (noteIndex === 0 && parent) {
- parent.insertBefore(ele, parent.childNodes[0])
- return
- }
- if (parent && parent.childNodes.length > 0) {
- let noteIndex2: number = 0
- for (let index = 0; index < parent.childNodes.length; index++) {
- const element = parent.childNodes[index]
- if (element.nodeName === 'note') {
- noteIndex2 = noteIndex2 + 1
- if (noteIndex2 === noteIndex) {
- parent.insertBefore(ele, element)
- break
- }
- }
- }
- }
- // console.log(noteIndex, parent)
- }
- /**
- * 检查传入文字是否为重复关键词
- * @param text 总谱xml
- * @returns 是否是重复关键词
- */
- export const isRepeatWord = (text: string): boolean => {
- if (text) {
- const innerText = text.toLocaleLowerCase()
- const dsRegEx: string = 'd\\s?\\.s\\.'
- const dcRegEx: string = 'd\\.\\s?c\\.'
- return (
- innerText === '@' ||
- StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + ' al fine', true) ||
- StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + ' al coda', true) ||
- StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + ' al fine', true) ||
- StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + ' al coda', true) ||
- StringUtil.StringContainsSeparatedWord(innerText, dcRegEx) ||
- StringUtil.StringContainsSeparatedWord(innerText, 'da\\s?capo', true) ||
- StringUtil.StringContainsSeparatedWord(innerText, dsRegEx, true) ||
- StringUtil.StringContainsSeparatedWord(innerText, 'dal\\s?segno', true) ||
- StringUtil.StringContainsSeparatedWord(innerText, 'al\\s?coda', true) ||
- StringUtil.StringContainsSeparatedWord(innerText, 'to\\s?coda', true) ||
- StringUtil.StringContainsSeparatedWord(innerText, 'a (la )?coda', true) ||
- StringUtil.StringContainsSeparatedWord(innerText, 'fine', true) ||
- StringUtil.StringContainsSeparatedWord(innerText, 'coda', true) ||
- StringUtil.StringContainsSeparatedWord(innerText, 'segno', true)
- )
- }
- return false
- }
- export const onlyVisible = (xml: string, partIndex: number): string => {
- if (!xml) return ''
- const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
- const partList = xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
- const partListNames = Array.from(partList).map(
- (item) => item.getElementsByTagName('part-name')?.[0].textContent || ''
- )
- const parts = xmlParse.getElementsByTagName('part') || []
- if (!parts.length) {
- // throw new Error('')
- return ''
- }
- // const firstTimeInfo = parts[0]?.getElementsByTagName('metronome')[0]?.parentElement?.parentElement?.cloneNode(true)
- const firstMeasures = [...parts[0]?.getElementsByTagName('measure')]
- const metronomes = [...parts[0]?.getElementsByTagName('metronome')]
- const words = [...parts[0]?.getElementsByTagName('words')]
- const rehearsals = [...parts[0]?.getElementsByTagName('rehearsal')]
- /** 第一分谱如果是约定的配置分谱则跳过 */
- if (partListNames[0]?.toLocaleUpperCase?.() === "COMMON") {
- partIndex++;
- partListNames.shift();
- }
- const visiblePartInfo = partList[partIndex]
- state.partListNames = partListNames
- if (visiblePartInfo) {
- const id = visiblePartInfo.getAttribute('id')
- Array.from(parts).forEach((part) => {
- if (part && part.getAttribute('id') !== id) {
- part.parentNode?.removeChild(part)
- // 不等于第一行才添加避免重复添加
- } else if (part && part.getAttribute('id') !== 'P1') {
- // 速度标记仅保留最后一个
- const metronomeData: {
- [key in string]: Element
- } = {}
- for (let i = 0; i < metronomes.length; i++) {
- const metronome = metronomes[i]
- const metronomeContainer = metronome.parentElement?.parentElement?.parentElement
- if (metronomeContainer) {
- const index = firstMeasures.indexOf(metronomeContainer)
- metronomeData[index] = metronome
- }
- }
- Object.values(metronomeData).forEach((metronome) => {
- const metronomeContainer = metronome.parentElement?.parentElement
- const parentMeasure = metronomeContainer?.parentElement
- const measureMetronomes = [...(parentMeasure?.childNodes || [])]
- const metronomesIndex = metronomeContainer ? measureMetronomes.indexOf(metronomeContainer) : -1
- // console.log(parentMeasure)
- if (parentMeasure && metronomesIndex > -1) {
- const index = firstMeasures.indexOf(parentMeasure)
- const activeMeasure = part.getElementsByTagName('measure')[index]
- setElementNoteBefore(metronomeContainer, parentMeasure, activeMeasure)
- // console.log(measureMetronomes, metronomesIndex, activeMeasure?.childNodes, activeMeasure?.childNodes[metronomesIndex])
- // activeMeasure?.insertBefore(metronomeContainer.cloneNode(true), activeMeasure?.childNodes[metronomesIndex])
- // // part.getElementsByTagName('measure')[index]?.appendChild(metronomeContainer.cloneNode(true))
- // // console.log(index, parentMeasure, firstMeasures.indexOf(parentMeasure))
- }
- })
- /** word比较特殊需要精确到note位置 */
- words.forEach((word) => {
- const text = word.textContent || ''
- if (
- (isSpecialMark(text) ||
- isSpeedKeyword(text) ||
- isGradientWords(text) ||
- isRepeatWord(text) ||
- GRADIENT_SPEED_RESET_TAG) &&
- text
- ) {
- const wordContainer = word.parentElement?.parentElement
- const parentMeasure = wordContainer?.parentElement
- const measureWords = [...(parentMeasure?.childNodes || [])]
- const wordIndex = wordContainer ? measureWords.indexOf(wordContainer) : -1
- if (wordContainer && parentMeasure && wordIndex > -1) {
- const index = firstMeasures.indexOf(parentMeasure)
- const activeMeasure = part.getElementsByTagName('measure')[index]
- setElementNoteBefore(wordContainer, parentMeasure, activeMeasure)
- // activeMeasure?.insertBefore(wordContainer.cloneNode(true), activeMeasure?.childNodes[wordIndex])
- }
- }
- })
- rehearsals.forEach((rehearsal) => {
- const container = rehearsal.parentElement?.parentElement
- const parentMeasure = container?.parentElement
- // console.log(rehearsal)
- if (parentMeasure) {
- const index = firstMeasures.indexOf(parentMeasure)
- part.getElementsByTagName('measure')[index]?.appendChild(container.cloneNode(true))
- // console.log(index, parentMeasure, firstMeasures.indexOf(parentMeasure))
- }
- })
- }
- // 最后一个小节的结束线元素不在最后 调整
- if (part && part.getAttribute('id') === id) {
- const barlines = part.getElementsByTagName('barline')
- const lastParent = barlines[barlines.length - 1]?.parentElement
- if (lastParent?.lastElementChild?.tagName !== 'barline') {
- const children = lastParent?.children || []
- for (let el of children) {
- if (el.tagName === 'barline') {
- // 将结束线元素放到最后
- lastParent?.appendChild(el)
- break
- }
- }
- }
- }
- })
- Array.from(partList).forEach((part) => {
- if (part && part.getAttribute('id') !== id) {
- part.parentNode?.removeChild(part)
- }
- })
- // 处理装饰音问题
- const notes = xmlParse.getElementsByTagName('note')
- const getNextvNoteDuration = (i: number) => {
- let nextNote = notes[i + 1]
- // 可能存在多个装饰音问题,取下一个非装饰音时值
- for (let index = i; index < notes.length; index++) {
- const note = notes[index]
- if (!note.getElementsByTagName('grace')?.length) {
- nextNote = note
- break
- }
- }
- const nextNoteDuration = nextNote?.getElementsByTagName('duration')[0]
- return nextNoteDuration
- }
- Array.from(notes).forEach((note, i) => {
- const graces = note.getElementsByTagName('grace')
- if (graces && graces.length) {
- // if (i !== 0) {
- note.appendChild(getNextvNoteDuration(i)?.cloneNode(true))
- // }
- }
- })
- }
- // console.log(new XMLSerializer().serializeToString(xmlParse))
- return new XMLSerializer().serializeToString(xmlParse)
- }
- // 倚音后连音线
- export const appoggianceFormate = (xml: string): string => {
- if (!xml) return xml
- const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
- const graces = xmlParse.querySelectorAll('grace')
- if (!graces.length) return xml
- const getNextElement = (el: HTMLElement): HTMLElement => {
- if (el.querySelector('grace')) {
- return getNextElement(el?.nextElementSibling as HTMLElement)
- }
- return el
- }
- for (let grace of graces) {
- const notations = grace.parentElement?.querySelector('notations')
- if (notations && notations.querySelectorAll('slur').length > 1) {
- let nextEle: Element = getNextElement(grace.parentElement?.nextElementSibling as HTMLElement)
- if (nextEle && nextEle.querySelectorAll('slur').length > 0) {
- const slurNumber = Array.from(nextEle.querySelector('notations')?.children || []).map((el: Element) => {
- return el.getAttribute('number')
- })
- const slurs = notations.querySelectorAll('slur')
- for (let nota of slurs) {
- if (!slurNumber.includes(nota.getAttribute('number'))) {
- nextEle.querySelector('notations')?.appendChild(nota)
- }
- }
- }
- }
- }
- return new XMLSerializer().serializeToString(xmlParse)
- }
- export const getVoicePartInfo = () => {
- const { MusicalInstrumentClassification, chinesePartName } = appState
- let subjectId = -1
- const { partListNames, partIndex } = state
- const filterPartNames = partListNames.filter((item) => (item || '').trim() !== '')
- if (filterPartNames.length) {
- for (const Classification of Object.entries(MusicalInstrumentClassification)) {
- const [key, value] = Classification as [string, string[]]
- const activePart = partListNames[partIndex]
- // console.log({activePart, value, partListNames})
- const filterValue = value.filter((item) => item && activePart.indexOf(item || '') > -1)
- if (activePart && (filterValue.length || value.includes(activePart))) {
- if (!isNaN(+key)) {
- subjectId = +key
- }
- return {
- realPartListNames: partListNames,
- subjectId: subjectId,
- partListNames: value,
- partName: activePart,
- chinesePartName: chinesePartName[activePart] || activePart,
- }
- }
- }
- }
- return {
- subjectId: subjectId,
- partListNames: [],
- }
- }
- /** 根据ID获取最顶级ID */
- export const isWithinScope = (tree: any[], id: number): number => {
- if (!tree) return 0
- let result = 0
- for (const item of tree) {
- if (item.id === id) {
- result = item.id
- break
- }
- if (item.sysMusicScoreCategoriesList) {
- result = isWithinScope(item.sysMusicScoreCategoriesList, id)
- if (result > 0) {
- result = item.id
- }
- if (result) break
- }
- }
- return result
- }
- /**
- * 特殊教材分类id
- */
- export const classids = [1, 30]
- /**
- * 指定id是否在给定的类别中
- * @param tree
- * @param id
- * @param parentInTree default: false
- */
- export const idIsInClassIds = (tree: any[], id: number, parentInTree = false): boolean => {
- if (!tree) return false
- let result = false
- for (const item of tree) {
- // console.log(id, item.id, (parentInTree || classids.includes(item.id)))
- if (item.id === id && (parentInTree || classids.includes(item.id))) {
- result = true
- break
- }
- if (item.sysMusicScoreCategoriesList) {
- result = idIsInClassIds(item.sysMusicScoreCategoriesList, id, parentInTree || classids.includes(item.id))
- if (result) break
- }
- }
- // console.log('result', result)
- return result
- }
- /**
- * 获取图片地址
- * @param src 图片地址
- * @returns
- */
- const getImageSize = (src: string): Promise<HTMLImageElement> => {
- return new Promise((resolve, reject) => {
- const img = new Image()
- img.src = src
- img.onload = () => {
- resolve(img)
- }
- img.onerror = (err) => reject(err)
- })
- }
- // 计算选择范围内的小节宽度高度
- export const setSettionBackground = () => {
- // console.log(1111)
- state.sectionBoundingBoxs = []
- const [start, end] = state.section.sort((a, b) => a.i - b.i)
- let startIndex = 0,
- endIndex = 0
- const selectNoteNum = Math.abs(end.i - start.i) + 1
- startIndex = 0
- endIndex = end.noteLength
- const seteds: number[] = []
- const heights: number[] = []
- for (let index = 0; index < selectNoteNum; index++) {
- const activeIndex = index + start.i
- const activeTimeNote = state.times[activeIndex]
- const activeNote = activeTimeNote.noteElement
- const measureListIndex = activeNote.sourceMeasure.measureListIndex
- // 如果没有节拍器,默认提前一个小节
- if (index === 0 && measureListIndex !== 0 && !state.needTick) {
- const firstNote = getFirsrNoteByMeasureListIndex(measureListIndex - 1)
- const fnote = firstNote?.noteElement
- if (fnote) {
- // console.log(fnote.sourceMeasure?.measureListIndex, start.noteElement?.sourceMeasure?.measureListIndex)
- for (
- let j = fnote.sourceMeasure?.measureListIndex;
- j < start.noteElement?.sourceMeasure?.measureListIndex;
- j++
- ) {
- if (!seteds.includes(j)) {
- for (const item of state.times) {
- if (item.noteElement?.sourceMeasure?.measureListIndex === j && !seteds.includes(j)) {
- const boundingBox = getBoundingBoxByNote(item.noteElement, {
- before: true,
- })
- state.befireSection = !boundingBox && state.befireSection ? state.befireSection : item
- if (!boundingBox) {
- continue
- }
- state.sectionBoundingBoxs.push(boundingBox)
- heights.push(boundingBox.height)
- seteds.push(j)
- }
- }
- }
- }
- }
- }
- if (!seteds.includes(measureListIndex)) {
- seteds.push(measureListIndex)
- const boundingBox = getBoundingBoxByNote(activeNote)
- if (boundingBox) {
- state.sectionBoundingBoxs.push(boundingBox)
- heights.push(boundingBox.height)
- }
- }
- }
- state.sectionBoundingBoxs.map((item) => {
- item.height = Math.max(...heights)
- return item
- })
- }
|