123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506 |
- import state from './state'
- import appState from '/src/state'
- import { browser } from '/src/helpers/utils'
- import runtime, { getFixTime } 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]
- 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
- 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, relativeTime}, 99999999)
- // 酷乐秀计算音符时值方法
- // 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)
- }
- // 如果是休止符并且整个小节休止,休止符的时值小于小节时值,取小节的时值
- 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
- 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) {
- 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
- }
- /** 如果是酷乐秀MIDI类型文件就不处理弱起 */
- if (state.activeDetail?.audioType === 'MIDI') {
- difftime = 0
- }
- fixtime += difftime
- }
- const nodeDetail = {
- 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: {
- 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: {
- 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.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
- ? {
- bbox: svgElelent.getBoundingBox?.() || '',
- top_y: svgElelent.top_y,
- note_height: svgElelent.note_height,
- }
- : '',
- 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({
- realKey: nodeDetail.realKey,
- NoteToGraphicalNoteObjectId: note.NoteToGraphicalNoteObjectId,
- })
- 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) => {
- 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 -1
- }
- let index = 0
- const ftime = times.filter((n, fi) => {
- const p: any = times[fi - 1]
- return p?.skipNextNote === false
- })
- for (let i = 0; i < ftime.length; i++) {
- const item = ftime[i]
- const prevItem = ftime[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] = time
- }
- }
- const activeNote = data[el.id || '']
- 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)
- })
- }
|