|
@@ -221,6 +221,27 @@
|
|
|
bucket_name="cloud-coach"
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
+
|
|
|
+ <div v-if="gradual && gradual.length">
|
|
|
+ <el-alert :closable="false" style="margin-bottom: 20px;">识别到共{{gradual.length}}处渐变速度,请输入对应小节时间信息</el-alert>
|
|
|
+ <div v-for="item in gradual">
|
|
|
+ <el-form-item
|
|
|
+ :label="item[0].measureIndex + ' 小节'"
|
|
|
+ :rules="[{required: true, message: '请输入合奏曲目时间'}]"
|
|
|
+ :prop="`graduals.${item[0].measureIndex}`"
|
|
|
+ >
|
|
|
+ <el-input placeholder="00:00:000" v-model="form.graduals[item[0].measureIndex]"></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item
|
|
|
+ :label="item[1].measureIndex + ' 小节'"
|
|
|
+ :rules="[{required: true, message: '请输入合奏曲目时间'}]"
|
|
|
+ :prop="`graduals.${item[1].measureIndex}`"
|
|
|
+ >
|
|
|
+ <el-input placeholder="00:00:000" v-model="form.graduals[item[1].measureIndex]"></el-input>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
<div
|
|
|
class="files"
|
|
|
v-for="(song, index) in form.sysMusicScoreAccompaniments"
|
|
@@ -398,12 +419,14 @@ export default {
|
|
|
props: ["detail", "type"],
|
|
|
data() {
|
|
|
return {
|
|
|
+ gradual: null,
|
|
|
xmlFirstSpeed: '',
|
|
|
partListNames: [],
|
|
|
tree: [],
|
|
|
extConfigJson: {},
|
|
|
memberRankList: [], // 会员列表
|
|
|
form: {
|
|
|
+ graduals: {},
|
|
|
rankIdType: 0, // 收费会员类型 默认免费
|
|
|
repeatedBeats: 0, // 重复节拍
|
|
|
sysMusicScore: {
|
|
@@ -453,6 +476,7 @@ export default {
|
|
|
this.extConfigJson = {...initailExtConfigJson}
|
|
|
}
|
|
|
this.form.repeatedBeats = this.extConfigJson.repeatedBeats;
|
|
|
+ this.form.gradual = this.extConfigJson.gradualTimes || {};
|
|
|
this.$set(this.form, "sysMusicScore", {
|
|
|
isOpenMetronome:Number(this.detail.isOpenMetronome),
|
|
|
name: this.detail.name,
|
|
@@ -475,6 +499,7 @@ export default {
|
|
|
this.form.rankIdType = 0;
|
|
|
}
|
|
|
const xmlres = await axios(this.detail.xmlUrl)
|
|
|
+ this.gradual = getGradualLengthByXml(xmlres.data)
|
|
|
this.partListNames = this.getPartListNames(xmlres.data)
|
|
|
this.FeatchDetailList();
|
|
|
} else {
|
|
@@ -496,6 +521,7 @@ export default {
|
|
|
const xmlRead = new FileReader()
|
|
|
xmlRead.onload = res => {
|
|
|
this.partListNames = this.getPartListNames(res.target.result)
|
|
|
+ this.partListNames = this.getPartListNames(res.target.result)
|
|
|
for (let j = 0; j < this.form.sysMusicScoreAccompaniments.length; j++) {
|
|
|
this.form.sysMusicScoreAccompaniments[j].track = this.partListNames[j]
|
|
|
if (!this.form.sysMusicScoreAccompaniments[j].speed) {
|
|
@@ -634,6 +660,7 @@ export default {
|
|
|
...this.form.sysMusicScore,
|
|
|
extConfigJson: JSON.stringify({
|
|
|
repeatedBeats: this.form.repeatedBeats,
|
|
|
+ gradualTimes: this.form.graduals,
|
|
|
}),
|
|
|
type: "COMMON",
|
|
|
showFlag: 0,
|
|
@@ -650,6 +677,7 @@ export default {
|
|
|
...this.form.sysMusicScore,
|
|
|
extConfigJson: JSON.stringify({
|
|
|
repeatedBeats: this.form.repeatedBeats,
|
|
|
+ gradualTimes: this.form.graduals,
|
|
|
}),
|
|
|
type: "COMMON",
|
|
|
id: this.detail.id,
|
|
@@ -668,6 +696,211 @@ export default {
|
|
|
},
|
|
|
},
|
|
|
};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取指定元素下一个Note元素
|
|
|
+ * @param ele 指定元素
|
|
|
+ * @param selectors 选择器
|
|
|
+ */
|
|
|
+ const getNextNote = (ele, selectors) => {
|
|
|
+ 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, partIndex) => {
|
|
|
+ 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')
|
|
|
+ // const firstTimeInfo = parts[0]?.getElementsByTagName('metronome')[0]?.parentElement?.parentElement?.cloneNode(true)
|
|
|
+ // const firstMeasures = [...Array.from(parts[0]?.getElementsByTagName('measure') || [])]
|
|
|
+ // const metronomes = [...Array.from(parts[0]?.getElementsByTagName('metronome') || [])]
|
|
|
+ // const words = [...Array.from(parts[0]?.getElementsByTagName('words') || [])]
|
|
|
+ // const codas = [...Array.from(parts[0]?.getElementsByTagName('coda') || [])]
|
|
|
+ // const rehearsals = [...Array.from(parts[0]?.getElementsByTagName('rehearsal') || [])]
|
|
|
+ const visiblePartInfo = partList[partIndex]
|
|
|
+ // console.log(visiblePartInfo, 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 {
|
|
|
+ // words.forEach(word => {
|
|
|
+ // const text = word.textContent || ''
|
|
|
+ // if(isSpeedKeyword(text) && text) {
|
|
|
+ // const wordContainer = word.parentElement?.parentElement?.parentElement
|
|
|
+ // if (wordContainer && wordContainer.firstElementChild && wordContainer.firstElementChild !== word) {
|
|
|
+ // const wordParent = word.parentElement?.parentElement
|
|
|
+ // const fisrt = wordContainer.firstElementChild
|
|
|
+ // wordContainer.insertBefore(wordParent, fisrt)
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 最后一个小节的结束线元素不在最后 调整
|
|
|
+ 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) => {
|
|
|
+ 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)
|
|
|
+}
|
|
|
+
|
|
|
+const speedInfo = {
|
|
|
+ '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,
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 按照xml进行减慢速度的计算
|
|
|
+ * @param xml 始终按照第一分谱进行减慢速度的计算
|
|
|
+ */
|
|
|
+ export function getGradualLengthByXml (xml) {
|
|
|
+ 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) => item.nodeName === 'note')
|
|
|
+ .findIndex((item) => item === note)
|
|
|
+
|
|
|
+ let allDuration = 0
|
|
|
+ let leftDuration = 0
|
|
|
+ for (let i = 0; i < measureNotes.length; i++) {
|
|
|
+ const n = 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),
|
|
|
+ type: ele.tagName,
|
|
|
+ allDuration,
|
|
|
+ leftDuration,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const gradualNotes = []
|
|
|
+ eles.sort((a, b) => a.index - b.index)
|
|
|
+ const keys = Object.keys(speedInfo).map(w => w.toLocaleLowerCase())
|
|
|
+ for (const ele of eles) {
|
|
|
+ const textContent = ele.textContent?.toLocaleLowerCase().trim()
|
|
|
+ if (ele.type === 'words' && keys.includes(textContent)) {
|
|
|
+ gradualNotes.push([
|
|
|
+ {
|
|
|
+ start: ele.index,
|
|
|
+ measureIndex: ele.measureIndex,
|
|
|
+ noteInMeasureIndex: ele.noteInMeasureIndex,
|
|
|
+ allDuration: ele.allDuration,
|
|
|
+ leftDuration: ele.leftDuration,
|
|
|
+ type: textContent,
|
|
|
+ },
|
|
|
+ ])
|
|
|
+ }
|
|
|
+ if (
|
|
|
+ ele.type === 'metronome' ||
|
|
|
+ (ele.type === 'words' && textContent === 'a tempo')
|
|
|
+ ) {
|
|
|
+ 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,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return gradualNotes
|
|
|
+}
|
|
|
</script>
|
|
|
<style lang="less" scoped>
|
|
|
.btns {
|