瀏覽代碼

合奏曲目减慢信息添加

wolyshaw 2 年之前
父節點
當前提交
965ca9fe80
共有 1 個文件被更改,包括 233 次插入0 次删除
  1. 233 0
      src/views/accompaniment/modals/form.vue

+ 233 - 0
src/views/accompaniment/modals/form.vue

@@ -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 {