index.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. /**
  2. * 音频合成节拍器
  3. */
  4. import Crunker from './crunker'
  5. import tickMp3 from './tick.mp3'
  6. import tockMp3 from './tock.mp3'
  7. import { getUploadSign, onOnlyFileUpload } from '@/utils/oss-file-upload'
  8. import { ref } from 'vue'
  9. const crunker = new Crunker()
  10. type musicSheetType = {
  11. audioFileUrl: string
  12. audioBeatMixUrl: null | string
  13. solmizationFileUrl: null | string
  14. solmizationBeatUrl: null | string
  15. }
  16. type taskAudioType = {
  17. obj: musicSheetType
  18. type: 'audioFileUrl' | 'solmizationFileUrl'
  19. audioBuff?: AudioBuffer
  20. }[]
  21. // 节拍器数据
  22. export const beatState = {
  23. times: [] as number[][],
  24. totalIndex: ref(0), // 总共需要处理的音频个数
  25. currentIndex: ref(0) // 当前处理了多少条数据
  26. }
  27. // 节拍器音源
  28. let tickMp3Buff: null | AudioBuffer = null
  29. let tockMp3Buff: null | AudioBuffer = null
  30. export default async function audioMergeBeats({
  31. musicSheetAccompanimentList,
  32. musicSheetSoundList
  33. }: {
  34. musicSheetAccompanimentList: musicSheetType[]
  35. musicSheetSoundList: musicSheetType[]
  36. }) {
  37. if (!beatState.times.length) return
  38. try {
  39. if (musicSheetSoundList.length + musicSheetAccompanimentList.length > 0) {
  40. // 扁平化数据 生成任务队列
  41. const taskAudio: taskAudioType = []
  42. ;[...musicSheetSoundList, ...musicSheetAccompanimentList].map((item) => {
  43. taskAudio.push({
  44. obj: item,
  45. type: 'audioFileUrl'
  46. })
  47. item.solmizationFileUrl && // 有唱名加上唱名
  48. taskAudio.push({
  49. obj: item,
  50. type: 'solmizationFileUrl'
  51. })
  52. })
  53. beatState.totalIndex.value = taskAudio.length
  54. /* 加载节拍器 */
  55. if (!tickMp3Buff || !tockMp3Buff) {
  56. const [tickMp3Bf, tockMp3Bf] = await crunker.fetchAudio(tickMp3, tockMp3)
  57. tickMp3Buff = tickMp3Bf
  58. tockMp3Buff = tockMp3Bf
  59. }
  60. /* 加上所有的音频文件 */
  61. await Promise.all(
  62. taskAudio.map(async (item) => {
  63. const [audioBuff] = await crunker.fetchAudio(item.obj[item.type]!)
  64. item.audioBuff = audioBuff
  65. })
  66. )
  67. /* 异步上传 */
  68. await new Promise((res) => {
  69. /* 合成音源 */
  70. taskAudio.map(async (item) => {
  71. const audioBlob = mergeBeats(item.audioBuff!)
  72. const url = await uploadFile(audioBlob)
  73. item.obj[item.type == 'audioFileUrl' ? 'audioBeatMixUrl' : 'solmizationBeatUrl'] = url
  74. beatState.currentIndex.value++
  75. if (beatState.currentIndex.value >= beatState.totalIndex.value) {
  76. res(null)
  77. }
  78. })
  79. })
  80. }
  81. } catch (err) {
  82. console.log('处理音频合成上传失败', err)
  83. }
  84. // 清空数据
  85. beatState.currentIndex.value = 0
  86. beatState.totalIndex.value = 0
  87. beatState.times = []
  88. }
  89. // 根据buffer合成音源返回blob
  90. function mergeBeats(audioBuff: AudioBuffer) {
  91. // 计算音频空白时间
  92. const silenceDuration = crunker.calculateSilenceDuration(audioBuff)
  93. const beats: AudioBuffer[] = []
  94. const currentTimes: number[] = []
  95. beatState.times.map((items) => {
  96. items.map((time, index) => {
  97. beats.push(index === 0 ? tickMp3Buff! : tockMp3Buff!)
  98. currentTimes.push(time + silenceDuration)
  99. })
  100. })
  101. //合并
  102. const mergeAudioBuff = crunker.mergeAudioBuffers([audioBuff, ...beats], [0, ...currentTimes])
  103. //转为 blob
  104. return crunker.audioBuffToBlob(mergeAudioBuff)
  105. }
  106. /**
  107. * 上传文件
  108. */
  109. async function uploadFile(audioBlob: Blob) {
  110. const filename = `${new Date().getTime()}.mp3`
  111. const { data } = await getUploadSign({
  112. filename,
  113. bucketName: 'cloud-coach',
  114. postData: {
  115. filename,
  116. acl: 'public-read',
  117. key: filename,
  118. unknowValueField: []
  119. }
  120. })
  121. const url = await onOnlyFileUpload('', {
  122. KSSAccessKeyId: data.KSSAccessKeyId,
  123. acl: 'public-read',
  124. file: audioBlob,
  125. key: filename,
  126. name: filename,
  127. policy: data.policy,
  128. signature: data.signature
  129. })
  130. return url
  131. }