import type { SelectOption } from 'naive-ui' import { NAlert, NButton, NCascader, NCheckbox, NCheckboxGroup, NDivider, NForm, NFormItem, NFormItemGi, NGi, NGrid, NInput, NInputGroup, NInputGroupLabel, NInputNumber, NModal, NRadio, NRadioGroup, NSelect, NSpace, NSpin, useDialog, useMessage } from 'naive-ui' import { defineComponent, onMounted, PropType, reactive, ref, watch } from 'vue' import { musicSheetCategoriesQueryTree, musicSheetDetail, musicSheetSave } from '../../api' import UploadFile from '@/components/upload-file' import styles from './index.module.less' import deepClone from '@/utils/deep.clone' import axios from 'axios' import { appKey, clientType, musicSheetSourceType } from '@/utils/constant' import { getMapValueByKey, getSelectDataFromObj } from '@/utils/objectUtil' import { musicalInstrumentPage } from '@views/system-manage/subject-manage/api' import { subjectPage } from '@views/system-manage/api' import MusicSheetOwnerDialog from '@views/music-library/music-sheet/modal/musicSheetOwnerDialog' import { sysApplicationPage } from '@views/menu-manage/api' import { filterPointCategory } from '@views/teaching-manage/unit-test' import MusicCreateImg from './music-create-img' import MusiceBeatTime from './musiceBeatTime' /** * 获取指定元素下一个Note元素 * @param ele 指定元素 * @param selectors 选择器 */ const getNextNote = (ele: any, selectors: any) => { let index = 0 const parentEle = ele.closest(selectors) let pointer = parentEle const measure = parentEle?.closest('measure') let siblingNote = null // 查找到相邻的第一个note元素 while (!siblingNote && index < (measure?.childNodes.length || 50)) { index++ if (pointer?.nextElementSibling?.tagName === 'note') { siblingNote = pointer?.nextElementSibling } pointer = pointer?.nextElementSibling } return siblingNote } export const onlyVisible = (xml: any, partIndex: any) => { if (!xml) return '' const xmlParse = new DOMParser().parseFromString(xml, 'text/xml') const partList = xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || [] const parts = xmlParse.getElementsByTagName('part') const visiblePartInfo = partList[partIndex] if (visiblePartInfo) { const id = visiblePartInfo.getAttribute('id') Array.from(parts).forEach((part) => { if (part && part.getAttribute('id') !== id) { part.parentNode?.removeChild(part) // 不等于第一行才添加避免重复添加 } // 最后一个小节的结束线元素不在最后 调整 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: any[] = (lastParent?.children as any) || [] 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: any) => { 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 } } return nextNote?.getElementsByTagName('duration')[0] } Array.from(notes).forEach((note, i) => { const graces = note.getElementsByTagName('grace') if (graces && graces.length) { note.appendChild(getNextvNoteDuration(i)?.cloneNode(true)) } }) } return new XMLSerializer().serializeToString(xmlParse) } const speedInfo = { 'rall.': 1.333333333, 'poco rit.': 1.333333333, 'rit.': 1.333333333, 'molto rit.': 1.333333333, 'molto rall': 1.333333333, molto: 1.333333333, lentando: 1.333333333, allargando: 1.333333333, morendo: 1.333333333, 'accel.': 0.8, calando: 2, 'poco accel.': 0.8, 'gradually slowing': 1.333333333, slowing: 1.333333333, slow: 1.333333333, slowly: 1.333333333, faster: 1.333333333 } /** * 按照xml进行减慢速度的计算 * @param xml 始终按照第一分谱进行减慢速度的计算 */ export function getGradualLengthByXml(xml: string) { const firstPartXml = onlyVisible(xml, 0) const xmlParse = new DOMParser().parseFromString(firstPartXml, 'text/xml') const measures = Array.from(xmlParse.querySelectorAll('measure')) const notes = Array.from(xmlParse.querySelectorAll('note')) const words = Array.from(xmlParse.querySelectorAll('words')) const metronomes = Array.from(xmlParse.querySelectorAll('metronome')) const eles = [] for (const ele of [...words, ...metronomes]) { const note = getNextNote(ele, 'direction') // console.log(ele, note) if (note) { const measure = note?.closest('measure') const measureNotes = Array.from(measure.querySelectorAll('note')) const noteInMeasureIndex = Array.from(measure.childNodes) .filter((item: any) => item.nodeName === 'note') .findIndex((item) => item === note) let allDuration = 0 let leftDuration = 0 for (let i = 0; i < measureNotes.length; i++) { const n: any = measureNotes[i] const duration = +(n.querySelector('duration')?.textContent || '0') allDuration += duration if (i < noteInMeasureIndex) { leftDuration = allDuration } } eles.push({ ele, index: notes.indexOf(note), noteInMeasureIndex, textContent: ele.textContent, // measureIndex: measures.indexOf(measure), //,measure?.getAttribute('number') measureIndex: measure?.getAttribute('number') ? Number(measure?.getAttribute('number')) : measures.indexOf(measure), type: ele.tagName, allDuration, leftDuration }) } } // 结尾处手动插入一个音符节点 eles.push({ ele: notes[notes.length - 1], index: notes.length, noteInMeasureIndex: 0, textContent: '', type: 'metronome', allDuration: 1, leftDuration: 1, measureIndex: measures.length }) const gradualNotes: any[] = [] eles.sort((a, b) => a.index - b.index) const keys = Object.keys(speedInfo).map((w) => w.toLocaleLowerCase()) let isLastNoteAndNotClosed = false for (const ele of eles) { const textContent: any = ele.textContent?.toLocaleLowerCase().trim() if (ele === eles[eles.length - 1]) { if (gradualNotes[gradualNotes.length - 1]?.length === 1) { isLastNoteAndNotClosed = true } } const isKeyWork = keys.find((k) => { const ks = k.split(' ') return textContent && ks.includes(textContent) }) if ( ele.type === 'metronome' || (ele.type === 'words' && (textContent.startsWith('a tempo') || isKeyWork)) || isLastNoteAndNotClosed ) { const indexOf = gradualNotes.findIndex((item) => item.length === 1) if (indexOf > -1 && ele.index > gradualNotes[indexOf]?.[0].start) { gradualNotes[indexOf][1] = { start: ele.index, measureIndex: ele.measureIndex, noteInMeasureIndex: ele.noteInMeasureIndex, allDuration: ele.allDuration, leftDuration: ele.leftDuration, type: textContent } } } if (ele.type === 'words' && isKeyWork) { gradualNotes.push([ { start: ele.index, measureIndex: ele.measureIndex, noteInMeasureIndex: ele.noteInMeasureIndex, allDuration: ele.allDuration, leftDuration: ele.leftDuration, type: textContent } ]) } } return gradualNotes } export type FormatXMLInfo = { speed: number title: string composer: string partNames: string[] } /** 获取xml基本信息,标题,速度等信息 */ export const getXmlInfo = (xml: string): FormatXMLInfo => { const data: FormatXMLInfo = { /** 速度 */ speed: 0, /** 标题 */ title: '', /** 作曲人 */ composer: '', /** 声部列表 */ partNames: [] } const xmlParse = new DOMParser().parseFromString(xml, 'text/xml') data.title = xmlParse.getElementsByTagName('work-title')[0]?.textContent || '' data.composer = xmlParse.getElementsByTagName('creator')[0]?.textContent || '' const measures = xmlParse.getElementsByTagName('measure') for (const item of Array.from(xmlParse.getElementsByTagName('part-name'))) { if (item.textContent) { data.partNames.push(item.textContent) } } for (const measure of Array.from(measures)) { const perMinute = measure.getElementsByTagName('per-minute') if (perMinute.length && perMinute[perMinute.length - 1]) { data.speed = parseFloat(perMinute[perMinute.length - 1].textContent || '0') break } } return data } export default defineComponent({ name: 'music-operation', props: { type: { type: String, default: 'add' }, data: { type: Object as PropType, default: () => {} }, tagList: { type: Array as PropType>, default: () => [] }, subjectList: { type: Array as PropType>, default: () => [] } // musicSheetCategories: { // type: Array as PropType>, // default: () => [] // } }, emits: ['close', 'getList'], setup(props, { slots, attrs, emit }) { const forms = reactive({ details: {} as any, // 曲目详情 graduals: {} as any, // 渐变速度 playMode: 'MP3', // 播放类型 xmlFileUrl: null, // XML midiUrl: null, // mid name: null, // 曲目名称 // musicTag: [] as any, // 曲目标签 composer: null as any, // 音乐人 playSpeed: null as any, // 曲谱速度 // showFingering: null as any, // 是否显示指法 // canEvaluate: null as any, // 是否评测 // notation: null as any, // 能否转和简谱 // auditVersion: null as any, // 审核版本 // sortNumber: null, // 排序 musicCover: null, // 曲谱封面 remark: null, // 曲谱描述 // musicSheetSoundList: [] as any, // 原音 musicSheetSoundList_YY: [] as any, // 演唱原音 musicSheetSoundList_YZ: [] as any, // 演奏演奏 musicSheetSoundList_all_subject: null, // 全部声部原音 // musicSheetCategoriesId: null, status: false, musicSheetType: 'CONCERT', // 曲目类型 sourceType: 'PLATFORM' as any, //来源类型/作者属性(PLATFORM: 平台; ORG: 机构; PERSON: 个人) // userId: null, // 所属人 appAuditFlag: 0, // 是否审核版本 midiFileUrl: null, // 伴奏文件 MIDI文件(保留字段) subjectIds: [] as any, // 可用声部 musicalInstrumentIdList: [] as any, //可用乐器 musicCategoryId: null, //曲目分类 musicSheetAccompanimentList: [] as any, //曲目伴奏 audioType: 'HOMEMODE', // 伴奏类型 isPlayBeat: true, // 是否播放节拍器 isUseSystemBeat: true, // 是否使用系统节拍器(0:否;1:是) isUseSingSystemBeat: true, //演唱是否使用系统节拍器(0:否;1:是) repeatedBeats: false, // 是否重复节拍时长 repeatedBeatsToSing: false, //演唱是否重复节拍时长 isPlaySingBeat: true, //是否播入演唱节拍器(0: 否 1:是) evaluationStandard: 'FREQUENCY', // 评分标准 节奏 AMPLITUDE 音准 FREQUENCY 分贝 DECIBELS multiTracksSelection: [] as any, // 声轨 musicSheetExtend: {} as any, //所属人信息 musicImg: '', // 五线谱图片 musicFirstImg: '', //首调图片 musicJianImg: '', // 简谱固定调 isEvxml: false, // 是否是evxml isShowFingering: true, // 是否显示指法 solmizationFileUrl: null, // 唱名文件 isAllSubject: false, // 适用声部 fSongList: [] as any, // 范唱列表 isScoreRender: true, // 总谱渲染 defaultScoreRender: true, // 演奏是否默认展示总谱渲染 scoreAudioFileUrl: null as any // 总谱文件 }) const state = reactive({ loading: false, musicUpdateLoading: false, // 是否在加载 中 previewMode: false, //是否是预览模式 tagList: [...props.tagList] as any, // 标签列表 xmlFirstSpeed: null as any, // 第一个音轨速度 partListNames: [] as any, // 所有音轨声部列表 musicSheetCategories: [] as any, musicSheetAccompanimentUrl: null as any, instrumentData: [], instrumentList: [] as any, instrumentIdNameMap: new Map() as any, subjectList: [] as any, showMusicSheetOwnerDialog: false, //所属人弹框 // musicSheetOwnerData: {}, //所属人信息 multiTracks: null as any, multiInstruments: null, appData: [], // 应用列表 ownerName: null as any, // 所属人名称描述 productOpen: false, // 是否打开自动生成图片 productItem: {} as any, productIfameSrc: '', isAutoSave: false, // 是否自动保存 bSongFile: null as any, // 伴唱 musicSheetSoundList: [] as any, subjectDisabled: false, // 声部不可用 instrumentDisabled: false, // 乐器不可用 initFSongMap: new Map() as any //初始化的范唱 }) const gradualData = reactive({ list: [] as any[], gradualRefs: [] as any[] }) const beatTimeData = reactive({ beatTimeOpen: false, musicId: '' }) watch( () => forms.multiTracksSelection, (value) => { // 在修改的时候不用自动更新 if(state.musicUpdateLoading) return initInstrumentAndSubjectByTrack(value) initFSongList() } ) const btnLoading = ref(false) const formsRef = ref() const message = useMessage() const dialog = useDialog() // 提交记录 const onSubmit = async () => { formsRef.value.validate(async (error: any) => { if (error) { return } // 校验合奏时声轨与乐器是否存在不匹配情况 if (forms.musicSheetType == 'CONCERT') { let set = [] as any const { data } = await musicalInstrumentPage({ page: 1, rows: 9999 }) data.rows.map((row: any) => { if (row.code) { row.code.split(',').forEach((code: string) => { set.push(code.replaceAll(' ', '').toLocaleLowerCase()) }) } }) let unDefinedTrack = [] as any forms.multiTracksSelection.forEach((item: any) => { if (item && !isPtrack(item)) { let contain = false let code = item.replace(' ', '').toLocaleLowerCase() for (let i = 0; i < set.length; i++) { if (code.startsWith(set[i])) { contain = true break } } if (!contain) { unDefinedTrack.push(item) } } }) if (unDefinedTrack.length > 0) { dialog.warning({ title: '提示', content: `声轨未配置:${unDefinedTrack.join(',')}`, positiveText: '确定', onPositiveClick: () => {} }) return } } try { //extConfigJson: {"repeatedBeats":0,"gradualTimes":{"75":"02:38:60","77":"02:43:39"}} let audioPlayTypes = new Set() as any let musicSheetSoundList = [] let musicSheetType = forms.musicSheetType let existYzFile = false // console.log({ // musicSheetType, // fSongList: forms.fSongList, // isAllSubject: forms.isAllSubject, // musicSheetSoundList_all_subject: forms.musicSheetSoundList_all_subject, // musicSheetSoundList_YZ: forms.musicSheetSoundList_YZ, // playMode: forms.playMode, // musicalInstrumentIdList: forms.musicalInstrumentIdList // }) if (musicSheetType == 'SINGLE') { if (forms.isAllSubject) { if (forms.musicSheetSoundList_all_subject) { audioPlayTypes.add('PLAY') musicSheetSoundList.push({ audioFileUrl: forms.musicSheetSoundList_all_subject, musicSheetId: props.data.id, audioPlayType: 'PLAY' }) existYzFile = true } } else { if (forms.musicSheetSoundList_YZ.length > 0) { forms.musicSheetSoundList_YZ.forEach((musicSheetSound: any) => { if ( forms.musicalInstrumentIdList.includes(musicSheetSound.musicalInstrumentId) && musicSheetSound.audioFileUrl ) { existYzFile = true musicSheetSoundList.push({ ...musicSheetSound, musicSheetId: props.data.id }) // 判断是否显示 if (!audioPlayTypes.has('PLAY')) { audioPlayTypes.add('PLAY') } } }) } } } else if (musicSheetType == 'CONCERT') { forms.musicSheetSoundList_YY.forEach((musicSheetSound: any) => { if ( musicSheetSound.audioFileUrl && forms.multiTracksSelection.includes(musicSheetSound.track) ) { musicSheetSoundList.push({ ...musicSheetSound, musicalInstrumentId: musicSheetSound.musicalInstrumentId || instrumentCodeToInstrumentId(musicSheetSound.track), musicSheetId: props.data.id, audioPlayType: 'PLAY' }) existYzFile = true if (!audioPlayTypes.has('PLAY')) { audioPlayTypes.add('PLAY') } } }) } // 判断 伴唱 范唱 唱名只有上传一个都可以上传 let existFSFile = false for (let i = 0; i < forms.fSongList.length; i++) { let fSong = forms.fSongList[i] if (fSong.audioFileUrl || fSong.solmizationFileUrl || fSong.femaleSolmizationFileUrl) { musicSheetSoundList.push(fSong) audioPlayTypes.add('SING') existFSFile = true } } // 总谱 伴唱判断 if (forms.scoreAudioFileUrl || state.bSongFile) { existFSFile = true } // 演唱 演奏只要有一个上传都可以 if (!existFSFile && !existYzFile) { message.warning('请上传音频文件') return } // 生成图片 if (!state.isAutoSave) { state.isAutoSave = true state.productOpen = true return } // 判断是否有总谱 if (forms.scoreAudioFileUrl) { forms.musicSheetAccompanimentList.push({ musicSheetId: props.data.id, audioFileUrl: state.bSongFile, scoreAudioFileUrl: forms.multiTracksSelection.length > 1 && forms.isScoreRender && forms.scoreAudioFileUrl ? forms.scoreAudioFileUrl : null, audioPlayType: 'SING' }) } if (state.musicSheetAccompanimentUrl) { forms.musicSheetAccompanimentList.push({ musicSheetId: props.data.id, audioFileUrl: state.musicSheetAccompanimentUrl, audioPlayType: 'PLAY' }) } let isScoreRender let defaultScoreRender if (forms.multiTracksSelection.length > 1) { isScoreRender = forms.isScoreRender if (forms.isScoreRender) { defaultScoreRender = forms.defaultScoreRender } } const obj = { musicCategoryId: forms.musicCategoryId, musicCover: forms.musicCover, name: forms.name, appAuditFlag: forms.appAuditFlag, subjectIds: forms.isAllSubject ? '' : forms.subjectIds.join(','), remark: forms.remark, audioPlayTypes: Array.from(audioPlayTypes).join(','), musicalInstrumentIds: forms.isAllSubject ? '' : forms.musicalInstrumentIdList.join(','), composer: forms.composer, musicSheetType: forms.musicSheetType, isUseSystemBeat: forms.isUseSystemBeat, isShowFingering: forms.isShowFingering, isPlayBeat: forms.isPlayBeat, multiTracksSelection: forms.multiTracksSelection.join(','), playSpeed: forms.playSpeed, playMode: forms.playMode, xmlFileUrl: forms.xmlFileUrl, musicImg: forms.musicImg, musicFirstImg: forms.musicFirstImg, musicJianImg: forms.musicJianImg, extConfigJson: JSON.stringify({ repeatedBeats: forms.repeatedBeats ? 1 : 0, gradualTimes: forms.graduals, isEvxml: forms.isEvxml ? 1 : 0, repeatedBeatsToSing: forms.repeatedBeatsToSing ? 1 : 0 }), // availableType: forms.availableType, sourceType: forms.sourceType, audioType: forms.audioType, status: forms.status, evaluationStandard: forms.evaluationStandard, musicSheetAccompanimentList: forms.musicSheetAccompanimentList, musicSheetSoundList: musicSheetSoundList, musicTag: '-1', isUseSingSystemBeat: forms.isUseSingSystemBeat, isPlaySingBeat: forms.isPlaySingBeat, isAllSubject: forms.isAllSubject, musicSheetExtend: forms.sourceType == 'PLATFORM' ? null : forms.musicSheetExtend, isScoreRender: isScoreRender, defaultScoreRender: defaultScoreRender } if (forms.audioType == 'MIDI') { obj.musicSheetSoundList = [] } btnLoading.value = true let resData: any if (props.type === 'add') { resData = await musicSheetSave(obj) } else if (props.type === 'edit') { resData = await musicSheetSave({ ...obj, id: props.data.id }) } beatTimeData.beatTimeOpen = true beatTimeData.musicId = resData.data } catch (e) { console.log(e) setTimeout(() => { btnLoading.value = false state.isAutoSave = false }, 100) } }) } // 合成节拍器的回调 function handlerMusiceBeatTimeClose() { if (props.type === 'add') { message.success('添加成功') } else if (props.type === 'edit') { message.success('修改成功') } emit('getList') emit('close') setTimeout(() => { btnLoading.value = false state.isAutoSave = false }, 100) } const isPtrack = async (track: string) => { if (track && (track == 'P1' || track == 'P2' || track == 'P3' || track == 'P4')) { return true } return false } const initFSongList = async () => { forms.fSongList.forEach((fSong: any) => { state.initFSongMap.set(fSong.track, fSong) }) let fSongList = [] as any forms.multiTracksSelection.forEach((item: any) => { if (state.initFSongMap.has(item)) { fSongList.push(state.initFSongMap.get(item)) } else { fSongList.push({ musicSheetId: props.data.id, audioFileUrl: null, audioPlayType: 'SING', musicalInstrumentId: null, track: item, // musicalInstrumentName: state.instrumentIdNameMap.get(item), musicalInstrumentName: null, solmizationFileUrl: null, // 唱名男 femaleSolmizationFileUrl: null // 唱名女 }) } }) forms.fSongList = fSongList } // 上传XML,初始化音轨 音轨速度 乐器、声部 const readFileInputEventAsArrayBuffer = (file: any) => { // 是否是evxml const xmlRead = new FileReader() xmlRead.onload = (res) => { try { gradualData.list = getGradualLengthByXml(res?.target?.result as any).filter( (item: any) => item.length === 2 ) // 音乐人中有值,则不做更新 const result = getXmlInfo(res?.target?.result as any) if (!forms.composer) { forms.composer = result.composer } } catch (error) {} forms.musicSheetSoundList_YY = forms.musicSheetSoundList_YY.filter((item: any) => { return ( !item.track || !containOther(item.track) || (item.track?.toLocaleUpperCase?.() != 'COMMON' && forms.multiTracksSelection.includes(item.track)) ) }) state.partListNames = getPartListNames(res?.target?.result as any) as any // parseInstrumentAndSubject(res?.target?.result as any) // 这里是如果没有当前音轨就重新写 let map = new Map() for (let i = 0; i < forms.musicSheetSoundList_YY.length; i++) { let track = forms.musicSheetSoundList_YY[i].track if (track) { map.set(track, forms.musicSheetSoundList_YY[i]) } } let newMusicSheetSoundList = [] let tracks = [] as any for (let j = 0; j < state.partListNames.length; j++) { let track = state.partListNames[j].value if (map.has(track)) { newMusicSheetSoundList.push(map.get(track)) } else { newMusicSheetSoundList.push({ audioFileUrl: null, track: track, musicalInstrumentId: null }) } tracks.push(track) if (!forms.multiTracksSelection.includes(track)) { forms.multiTracksSelection.push(track) } } for (let i = 0; i < forms.musicSheetSoundList_YY.length; i++) { let track = forms.musicSheetSoundList_YY[i].track if (!track || !tracks.includes(track)) { forms.musicSheetSoundList_YY[i].track = null newMusicSheetSoundList.push(forms.musicSheetSoundList_YY[i]) } } forms.musicSheetSoundList_YY = newMusicSheetSoundList forms.multiTracksSelection = forms.multiTracksSelection.filter((track: any) => { return tracks.includes(track) }) // 全选选中 state.multiTracks = 'all' // 循环添加所在音轨的原音 // for (let index = forms.musicSheetSoundList.length; index < state.partListNames.length; index++) { // const part = state.partListNames[index].value // const sysData = { // ...forms.musicSheetSoundList[0], // track: part // } // if (!sysData.speed) { // sysData.speed = state.xmlFirstSpeed // } // createSys(sysData) // } if (forms.musicSheetSoundList_YY.length == 0) { forms.musicSheetSoundList_YY.push({ audioFileUrl: '', track: '' }) } } xmlRead.readAsText(file) } const containOther = (track: any) => { for (let i = 0; i < state.partListNames.length; i++) { if (state.partListNames[i].value == track) { return true } } return false } const validSoundNum = () => { return forms.musicSheetSoundList_YY.filter((item: any) => { return ( !item.track || !containOther(item.track) || (item.track?.toLocaleUpperCase?.() != 'COMMON' && forms.multiTracksSelection.includes(item.track)) ) }).length } const parseInstrumentAndSubject = (xml: any) => { if (!xml) return const xmlParse = new DOMParser().parseFromString(xml, 'text/xml') // 乐器 const instrumentCodeList: any = [] const instrumentEle = xmlParse.getElementsByTagName('virtual-instrument') for (let index = 0; index < instrumentEle.length; index++) { const note = instrumentEle[index] let instrumentCode = note.getElementsByTagName('virtual-name')?.[0]?.textContent || '' instrumentCode = instrumentCode.toLocaleLowerCase().trim() if (instrumentCode && !instrumentCodeList.includes(instrumentCode)) { instrumentCodeList.push(instrumentCode) } } // 乐器支持多编码,暂不开放 initInstrumentAndSubjectByTrack(instrumentCodeList) } /** 通过声轨反显乐器和声部 */ const initInstrumentAndSubjectByTrack = async (tracks: string[]) => { // 选择一个声轨,独奏 // 选择了多个声轨,合奏,乐器和声部自动反显,不可修改 if (!tracks || tracks.length <= 1) { forms.musicSheetType = 'SINGLE' state.subjectDisabled = false state.instrumentDisabled = false return } forms.musicSheetType = 'CONCERT' state.subjectDisabled = true state.instrumentDisabled = true await initInstrumentAndSubjectByCode(tracks) } /** 获取分轨名称 */ const getInstrumentName = (instruments: any, name = '') => { name = name.toLocaleLowerCase().replace(/ /g, '').replace(/\d*/gi, '') if (!name) return '' for (let key of instruments) { const _key = key.toLocaleLowerCase().replace(/ /g, '') // if (_key.includes(name)) { // return key // } if (_key === name) { return _key } } // for (let key of instruments) { // const _key = key.toLocaleLowerCase().replace(/ /g, '') // if (name.includes(_key)) { // return key // } // } return '' } // 通过乐器编码反显乐器和声部 const initInstrumentAndSubjectByCode = async (codes: string[]) => { // forms.musicalInstrumentIdList = [] forms.subjectIds = [] const codeIdMap = new Map() as any const codeMapKeys: string[] = [] state.instrumentData.forEach((data: any) => { if (!data.disabled) { const codes = data.code.split(/[,,]/) codes.forEach((code: string) => { let codeTemp = code.replaceAll(' ', '').toLowerCase() codeMapKeys.push(codeTemp) if (codeIdMap.has(codeTemp)) { codeIdMap.get(codeTemp).push(data.id + '') } else { const arr = [] as any arr.push(data.id + '') codeIdMap.set(codeTemp, arr) } }) } }) for (let i = 0; i < codes.length; i++) { let code = codes[i].replaceAll(' ', '').toLowerCase() const tempCode = getInstrumentName(codeMapKeys, code) if (codeIdMap.has(tempCode)) { codeIdMap.get(tempCode).forEach((c: any) => { const index = state.instrumentList.findIndex((item: any) => item.id.toString() === c) if (!forms.musicalInstrumentIdList.includes(c) && index !== -1) { forms.musicalInstrumentIdList.push(c) } }) } } // 声部 if (forms.musicalInstrumentIdList.length > 0) { await showBackSubject(forms.musicalInstrumentIdList) // changeSubject(forms.subjectIds) } } // 通过乐器编码返回乐器编号 const instrumentCodeToInstrumentId = (code: string) => { const codeIdMap = new Map() as any const codeMapKeys: string[] = [] state.instrumentData.forEach((data: any) => { if (!data.disabled) { const codes = data.code.split(/[,,]/) codes.forEach((code: string) => { let codeTemp = code.replaceAll(' ', '').toLowerCase() codeMapKeys.push(codeTemp) if (codeIdMap.has(codeTemp)) { codeIdMap.get(codeTemp).push(data.id + '') } else { const arr = [] as any arr.push(data.id + '') codeIdMap.set(codeTemp, arr) } }) } }) if (!code) { return '' } code = code.replaceAll(' ', '').toLowerCase() const tempCode = getInstrumentName(codeMapKeys, code) if (codeIdMap.has(tempCode)) { const result = codeIdMap.get(tempCode) console.log('result:', result) return result[0] || '' } return '' } // 获取xml中所有轨道 乐器 const getPartListNames = (xml: any) => { if (!xml) return [] const xmlParse = new DOMParser().parseFromString(xml, 'text/xml') const partList: any = xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || [] let partListNames = Array.from(partList).map((item: any) => { let part = item.getElementsByTagName('part-name')?.[0] // evxml没有分轨,需要手动设置一个默认的名称,用于上传原音 let track = '' if (forms.isEvxml) { // 秒极客曲目,取ID let id = item.getAttribute('id') if (id) { track = id } } else { // 优先解析声轨,没有就取id值 if (part) { track = part.textContent || '' } else { let id = item.getAttribute('id') if (id) { track = id } } } return { value: track.trim(), label: track.trim() } }) // 处理空数据 // if (partListNames.length === 1 && forms.details.id && !partListNames[0].value) { // partListNames[0] = { // value: forms.details.multiTracksSelection, // label: forms.details.multiTracksSelection // } // } partListNames = partListNames.filter((n: any) => n.value?.toLocaleUpperCase?.() != 'COMMON') // if (partListNames.length > 0) { // forms.musicSheetSoundList = forms.musicSheetSoundList.slice(0, partListNames.length) // } state.xmlFirstSpeed = xmlParse.getElementsByTagName('per-minute')?.[0]?.textContent || '' if (!forms.playSpeed) { if (state.xmlFirstSpeed) { forms.playSpeed = Number.parseFloat(state.xmlFirstSpeed) } else { // 速度默认给100 forms.playSpeed = 100 } } // console.log('xml声轨',partListNames,state.xmlFirstSpeed) return partListNames } // 判断选择的音轨是否在选中 const initPartsListStatus = (track: string): any => { // const _names = state.partListNames.filter( // (n: any) => n.value?.toLocaleUpperCase?.() != 'COMMON' // ) const partListNames = deepClone(state.partListNames) || [] const multiTracksSelection = forms.multiTracksSelection partListNames.forEach((item: any) => { if (multiTracksSelection.includes(item.value)) { item.disabled = true } else { item.disabled = false } // const index = forms.musicSheetSoundList.findIndex( // (ground: any) => item.value == ground.track // ) // if (index > -1 && track == item.value) { // item.disabled = false // } else { // item.disabled = true // } }) return partListNames || [] } // 反显声部 const showBackSubject = async (musicalInstrumentIdList: []) => { if (!musicalInstrumentIdList || musicalInstrumentIdList.length == 0) { forms.subjectIds = [] return } try { const { data } = await subjectPage({ page: 1, rows: 999, musicalInstrumentIdList: musicalInstrumentIdList }) const tempList = data.rows || [] const tempSubject: any[] = [] tempList.forEach((item: any) => { tempSubject.push(item.id + '') }) forms.subjectIds = tempSubject } catch {} } // 添加原音 const createSys = (initData?: any) => { forms.musicSheetSoundList_YY.push({ audioFileUrl: null, // 原音 track: null, // 轨道 ...initData }) } // 删除原音 const removeSys = (index: number) => { dialog.warning({ title: '提示', content: `是否确认删除此原音?`, positiveText: '确定', negativeText: '取消', onPositiveClick: async () => { const sound = forms.musicSheetSoundList_YY[index] let track = sound.track // if (!track) { // track = '' // } // if (track) { const selectIndex = forms.multiTracksSelection.indexOf(track) if (selectIndex > -1) { forms.multiTracksSelection.splice(selectIndex, 1) } else { forms.musicSheetSoundList_YY.splice(index, 1) } if ( state.multiTracks == 'all' && state.partListNames.length != forms.multiTracksSelection.length ) { state.multiTracks = null } } }) } const checkMultiTracks = (value: string) => { if (!value) { return } let tempMultiTracksSelection = [] as any if (value === 'all') { state.partListNames.forEach((next: any) => { tempMultiTracksSelection.push(next.value) }) } else if (value === 'invert') { state.partListNames.forEach((next: any) => { if (!forms.multiTracksSelection.includes(next.value)) { tempMultiTracksSelection.push(next.value) } }) } forms.multiTracksSelection = tempMultiTracksSelection } const setOwnerName = () => { if (forms.sourceType == 'PLATFORM') { state.ownerName = '' return } if (!forms.sourceType || !forms.musicSheetExtend?.userId) { return } const appId = forms.musicSheetExtend.applicationId const app = state.appData.filter((next: any) => { return next.id == appId }) as any if (app.length > 0) { state.ownerName = app[0].appName } if (forms.sourceType == 'ORG') { state.ownerName = state.ownerName ? state.ownerName + '-' + forms.musicSheetExtend.organizationRole : forms.musicSheetExtend.organizationRole } else if (forms.sourceType == 'PERSON') { state.ownerName = state.ownerName ? state.ownerName + '-' + getMapValueByKey(forms.musicSheetExtend.clientType, new Map(Object.entries(clientType))) : getMapValueByKey(forms.musicSheetExtend.clientType, new Map(Object.entries(clientType))) if (forms.musicSheetExtend.userName) { state.ownerName = state.ownerName ? state.ownerName + '-' + forms.musicSheetExtend.userName : forms.musicSheetExtend.userName } if (forms.musicSheetExtend.phone) { state.ownerName += '(' + forms.musicSheetExtend.phone + ')' } } } // 声轨数据兼容 const formatTrack = (track: string) => { if (!track) { return '' } const trim = track.trim().toUpperCase() // 导入后的脏数据兼容 if (trim == 'P1' || trim == 'NULL') { return '' } return track.trim() } const changeSubject = async (subjectIdList: []) => { state.instrumentList = [] if (!subjectIdList || subjectIdList.length == 0) { forms.musicalInstrumentIdList = [] return } let enableFlag = null if (props.type === 'add') { enableFlag = true } let tempMusicalInstrumentIdList = [] as any const { data } = await musicalInstrumentPage({ page: 1, rows: 999, subjectIds: subjectIdList, enableFlag: enableFlag }) data.rows.map((item: any) => { tempMusicalInstrumentIdList.push(item.id + '') state.instrumentList.push({ label: item.name, value: item.id + '', disabled: !item.enableFlag }) }) forms.musicalInstrumentIdList = forms.musicalInstrumentIdList.filter((item: any) => { return tempMusicalInstrumentIdList.includes(item) }) } onMounted(async () => { state.loading = true if (props.type === 'preview') { state.previewMode = true } // 获取乐器信息 { if (state.instrumentList && state.instrumentList.length > 0) { return } try { const { data } = await musicalInstrumentPage({ page: 1, rows: 999 }) const tempList = data.rows || [] tempList.forEach((item: any) => { item.label = item.name item.value = item.id + '' // item.disabled = !item.enableFlag state.instrumentIdNameMap.set(item.id + '', item.name) forms.musicSheetSoundList_YZ.push({ musicSheetId: props.data.id, musicalInstrumentId: item.id + '', musicalInstrumentName: item.name, audioFileUrl: null, audioPlayType: 'PLAY' }) }) state.instrumentData = tempList state.instrumentList = deepClone(tempList) } catch {} } state.subjectList = deepClone(props.subjectList) state.subjectList.forEach((subject: any) => { subject.disabled = !subject.enableFlag }) // 初始化应用 { const appKeys = Object.keys(appKey) const { data } = await sysApplicationPage({ page: 1, rows: 999, parentId: 0 }) const tempList = data.rows || [] const filter = tempList.filter((next: any) => { return appKeys.includes(next.appKey) }) filter.forEach((item: any) => { item.label = item.appName item.value = item.id }) state.appData = filter } // 获取分类信息 { try { const { data } = await musicSheetCategoriesQueryTree({ enable: true }) state.musicSheetCategories = filterPointCategory(data, 'musicSheetCategoriesList') } catch (e) {} } if (props.type === 'edit' || props.type === 'preview') { const detail = props.data try { const { data } = await musicSheetDetail({ id: detail.id }) forms.details = data forms.playMode = data.playMode forms.xmlFileUrl = data.xmlFileUrl forms.midiUrl = data.midiUrl forms.name = data.name // forms.musicTag = data.musicTag?.split(',') forms.composer = data.composer forms.playSpeed = data.playSpeed ? Number.parseFloat(data.playSpeed) : 100 // forms.showFingering = Number(data.showFingering) // forms.canEvaluate = Number(data.canEvaluate) // forms.notation = Number(data.notation) // forms.auditVersion = Number(data.auditVersion) // forms.sortNumber = data.sortNumber forms.defaultScoreRender = data.defaultScoreRender forms.isScoreRender = data.isScoreRender forms.musicCover = data.musicCover forms.remark = data.remark forms.status = data.status // forms.musicSheetType = data.musicSheetType || 'SINGLE' forms.sourceType = data.sourceType forms.appAuditFlag = data.appAuditFlag ? 1 : 0 forms.midiFileUrl = data.midiFileUrl forms.isShowFingering = data.isShowFingering forms.isAllSubject = data.isAllSubject forms.isUseSingSystemBeat = data.isUseSingSystemBeat forms.isPlaySingBeat = data.isPlaySingBeat forms.subjectIds = [] if (data.subjectIds) { const subjectIds = data.subjectIds.split(',') || [] subjectIds.forEach((subjectId: any) => { if (!forms.subjectIds.includes(subjectId)) { forms.subjectIds.push(subjectId) } }) state.subjectList = state.subjectList.filter((subject: any) => { return !subject.disabled || subjectIds.includes(subject.value) }) } forms.musicCategoryId = data.musicCategoryId forms.audioType = data.audioType forms.isPlayBeat = data.isPlayBeat forms.isUseSystemBeat = data.isUseSystemBeat // 获取渐变 和 是否多声部 try { const extConfigJson = data.extConfigJson ? JSON.parse(data.extConfigJson) : {} forms.graduals = extConfigJson.gradualTimes || {} forms.repeatedBeats = !!extConfigJson.repeatedBeats forms.isEvxml = !!extConfigJson.isEvxml forms.repeatedBeatsToSing = !!extConfigJson.repeatedBeatsToSing } catch (error) {} forms.evaluationStandard = data.evaluationStandard forms.musicSheetExtend = data.musicSheetExtend state.musicSheetSoundList = data.musicSheetSoundList ? data.musicSheetSoundList : [] let musicSheetAccompanimentList = data.musicSheetAccompanimentList ? data.musicSheetAccompanimentList : [] musicSheetAccompanimentList.forEach((next: any) => { let audioPlayType = next.audioPlayType if (audioPlayType && audioPlayType == 'SING') { state.bSongFile = next.audioFileUrl forms.scoreAudioFileUrl = next.scoreAudioFileUrl } else { state.musicSheetAccompanimentUrl = next.audioFileUrl } }) // 初始化演奏 for (let i = 0; i < state.musicSheetSoundList.length; i++) { if ( state.musicSheetSoundList[i].audioPlayType == 'SING' && state.musicSheetSoundList[i].track != null ) { // 范唱 唱名 state.initFSongMap.set(state.musicSheetSoundList[i].track, { ...state.musicSheetSoundList[i] // musicalInstrumentName: state.instrumentIdNameMap.get(state.musicSheetSoundList[i].musicalInstrumentId), }) } else { if (forms.isAllSubject) { forms.musicSheetSoundList_all_subject = state.musicSheetSoundList[i].audioFileUrl } else { // 乐器演奏原音 for (let j = 0; j < forms.musicSheetSoundList_YZ.length; j++) { let musicalInstrumentId = state.musicSheetSoundList[i].musicalInstrumentId if ( musicalInstrumentId && musicalInstrumentId == forms.musicSheetSoundList_YZ[j].musicalInstrumentId ) { forms.musicSheetSoundList_YZ[j].audioFileUrl = state.musicSheetSoundList[i].audioFileUrl } } } } } forms.musicalInstrumentIdList = data.musicalInstrumentIds ? data.musicalInstrumentIds.split(',') : [] // 乐器下拉格式化,停用的过滤 state.instrumentList = state.instrumentList.filter((next: any) => { return next.enableFlag || forms.musicalInstrumentIdList.includes(next.id + '') }) setOwnerName() axios.get(data.xmlFileUrl).then((res: any) => { if (res?.data) { state.musicUpdateLoading = true; gradualData.list = getGradualLengthByXml(res?.data as any).filter( (item: any) => item.length === 2 ) state.partListNames = getPartListNames(res?.data as any) as any // 初始化音轨和原音 let multiTracksSelection = [] as any if (data.multiTracksSelection) { data.multiTracksSelection = data.multiTracksSelection.toLocaleUpperCase() } if ( !data.multiTracksSelection || data.multiTracksSelection.trim() == '' || data.multiTracksSelection.trim() == 'NULL' ) { multiTracksSelection.push('') } else { data.multiTracksSelection.split(',').forEach((next: any) => { multiTracksSelection.push(next.trim()) }) } let names = state.partListNames.map((next: any) => next.label) multiTracksSelection = names.filter((next: any) => multiTracksSelection.includes(next.toLocaleUpperCase()) ) forms.multiTracksSelection = multiTracksSelection // 根据声轨数量判断独奏合奏 forms.musicSheetType = forms.multiTracksSelection.length > 1 ? 'CONCERT' : 'SINGLE' const existSoundList = data.musicSheetSoundList ? data.musicSheetSoundList : [] // 如果只有一个原音文件,并且原音没有对应声轨,取xml解析中的第一个声轨绑定当当前原音 // if (existSoundList.length === 1 && !formatTrack(existSoundList[0].track)) { // let track = state.partListNames.length > 0 ? state.partListNames[0].value : null; // forms.musicSheetSoundList_YY.push({ // audioFileUrl: existSoundList[0].audioFileUrl, // 原音 // musicalInstrumentId: existSoundList[0].musicalInstrumentId, // track: track, // 轨道 // audioPlayType: 'PLAY' // }) // if (track && !forms.multiTracksSelection.includes(track)) { // forms.multiTracksSelection.push(track) // } // } else { const tracks = [] as any state.partListNames.forEach((item: any) => { let audioFileUrl = null let musicalInstrumentId = null if (forms.musicSheetType == 'CONCERT') { existSoundList.forEach((next: any) => { if (next.audioPlayType == 'PLAY') { let track = '' if (next.track) { track = next.track.trim() } if (track == item.value) { audioFileUrl = next.audioFileUrl musicalInstrumentId = next.musicalInstrumentId } } }) } forms.musicSheetSoundList_YY.push({ audioFileUrl: audioFileUrl, // 原音 musicalInstrumentId: musicalInstrumentId, // 乐器 track: item.value, // 轨道 audioPlayType: 'PLAY' }) tracks.push(item.value) }) if (tracks.length == forms.multiTracksSelection.length) { state.multiTracks = 'all' } // 处理没有声轨,但有原音 if (forms.musicSheetType == 'CONCERT') { state.musicSheetSoundList.forEach((next: any) => { if (next.audioPlayType == 'PLAY') { if ( next.track && !tracks.includes(next.track.trim()) && next.audioPlayType == 'PLAY' ) { forms.musicSheetSoundList_YY.push({ audioFileUrl: next.audioFileUrl, // 原音 musicalInstrumentId: next.musicalInstrumentId, track: next.track ? next.track.trim() : '', // 轨道 audioPlayType: 'PLAY' }) } } }) // } } state.musicUpdateLoading = false } }) } catch (error) {} } else { // 新增只能使用启用状态的数据 state.subjectList = state.subjectList.filter((next: any) => { return next.enableFlag == true }) state.instrumentList = state.instrumentList.filter((next: any) => { return next.enableFlag == true }) } state.loading = false }) return () => (
基本信息 { // 发送变化,清理选择的所属人信息 forms.musicSheetExtend = {} state.ownerName = null // forms.musicSheetExtend.userId = null // forms.musicSheetExtend.userName = null // forms.musicSheetExtend.applicationId = null // forms.musicSheetExtend.organizationRoleId = null }} /> {forms.sourceType === 'PERSON' && ( { state.showMusicSheetOwnerDialog = true }} > {state.ownerName ? state.ownerName : '请选择所属人'} )} {forms.sourceType === 'ORG' && ( { state.showMusicSheetOwnerDialog = true }} > {state.ownerName ? state.ownerName : '请选择所属机构'} )} 曲目设置 { if (value === 'MP3') { forms.playMode = 'MP3' } else { forms.playMode = 'MIDI' } }} > MP3 {forms.playMode == 'MIDI' && props.type === 'preview' && ( MID )} {forms.playMode === 'MP3' && ( 自制伴奏 普通伴奏 )} { // forms.multiTracksSelection = [] // state.partListNames = [] // forms.musicSheetSoundList_YY = [] // forms.musicalInstrumentIdList = [] // forms.subjectIds = [] }} /> 标准评测 打击乐(振幅) 节奏(分贝) {/**/} {/* */} {/* 多声轨*/} {/* 单声轨*/} {/* */} {/**/} {!forms.isAllSubject && ( { await showBackSubject(value) }} /> { // await changeSubject(val) }} /> )} { checkMultiTracks(value) }} > 全选 重置 反选 {state.partListNames && state.partListNames.length > 0 && ( { if (state.partListNames.length != val.length) { state.multiTracks = null } else { state.multiTracks = 'all' } }} > {state.partListNames.map((item: any) => ( ))} )} {forms.multiTracksSelection.length > 1 && ( 支持 不支持 {/* 支持总谱渲染的时候才显示 */} {forms.isScoreRender && ( 总谱 分轨 )} )} 演唱文件 {forms.isPlaySingBeat && ( 系统节拍器 MP3节拍器 )} {forms.fSongList.length > 0 && forms.fSongList.map((item: any) => { return ( ) })} {forms.multiTracksSelection.length > 1 && forms.isScoreRender && ( )} {forms.fSongList.length > 0 && forms.fSongList.map((item: any) => { return ( ) })} 演奏文件 {forms.isPlayBeat && ( 系统节拍器 MP3节拍器 )} {forms.playMode === 'MP3' && ( )} {forms.isAllSubject && forms.musicSheetType == 'SINGLE' && ( {}} /> )} {/*渐变速*/} {!!gradualData.list.length && ( <> 识别到共1处渐变速度,请输入Dorico对应小节时间信息 {gradualData.list.map((n: any, ni: number) => ( {n[0].measureIndex}小节开始
~
{n[1].measureIndex}小节结束
))}
)} {/*独奏*/} {forms.musicSheetType == 'SINGLE' && forms.playMode === 'MP3' && !forms.isAllSubject && forms.musicSheetSoundList_YZ.map((item: any, index: any) => { return ( <> {forms.musicalInstrumentIdList.includes(item.musicalInstrumentId) && ( )} ) })} {/*合奏*/} {forms.musicSheetType == 'CONCERT' && forms.playMode === 'MP3' && forms.musicSheetSoundList_YY.length > 0 && ( <> {forms.musicSheetSoundList_YY.map((item: any, index: number) => { return ( <> {(!containOther(item.track) || (item.track?.toLocaleUpperCase?.() != 'COMMON' && forms.multiTracksSelection.includes(item.track))) && ( -1} > -1, required: false, message: `请上传${ item.track ? item.track + '的' : '第' + (index + 1) + '个' }原音` } ]} > {state.partListNames.length > 0 && ( { const track = item.track if (track) { // 声轨交换 forms.musicSheetSoundList_YY.forEach((next: any) => { if (next.track == value) { next.track = track } }) const index = forms.multiTracksSelection.indexOf(item.track) forms.multiTracksSelection.splice(index, 1) } else { forms.musicSheetSoundList_YY = forms.musicSheetSoundList_YY.filter((next: any) => { return next.track != value }) } if ( value != null && !forms.multiTracksSelection.includes(value) ) { forms.multiTracksSelection.push(value) } item.track = value if ( state.partListNames.length == forms.multiTracksSelection.length ) { state.multiTracks = 'all' } }} /> )} removeSys(index)} > 删除 )} ) })} )}
{props.type !== 'preview' && ( emit('close')}> 取消 onSubmit()} loading={btnLoading.value} disabled={btnLoading.value} > 确认 )} { state.showMusicSheetOwnerDialog = false }} onChoseMusicSheetOwnerData={(musicSheetOwnerData) => { forms.musicSheetExtend = { ...musicSheetOwnerData } setOwnerName() }} /> { state.isAutoSave = false }} showIcon={false} > (state.productOpen = false)} onConfirm={async (item: any) => { // 保存 try { forms.musicImg = item.musicImg forms.musicFirstImg = item.musicFirstImg forms.musicJianImg = item.musicJianImg await onSubmit() } catch (e: any) { // console.log(e, 'e') } // setTimeout(() => { // state.isAutoSave = false // }, 50) }} /> {beatTimeData.beatTimeOpen && ( )}
) } })