/** * 音频合成节拍器 */ import Crunker from './crunker' import tickMp3 from './tick.mp3' import tockMp3 from './tock.mp3' import { getUploadSign, onOnlyFileUpload } from '@/utils/oss-file-upload' import { ref } from 'vue' const crunker = new Crunker() type musicSheetType = { audioFileUrl: string audioBeatMixUrl: null | string solmizationFileUrl: null | string solmizationBeatUrl: null | string } type taskAudioType = { obj: musicSheetType type: 'audioFileUrl' | 'solmizationFileUrl' audioBuff?: AudioBuffer }[] // 节拍器数据 export const beatState = { times: [] as number[][], totalIndex: ref(0), // 总共需要处理的音频个数 currentIndex: ref(0) // 当前处理了多少条数据 } // 节拍器音源 let tickMp3Buff: null | AudioBuffer = null let tockMp3Buff: null | AudioBuffer = null export default async function audioMergeBeats({ musicSheetAccompanimentList, musicSheetSoundList }: { musicSheetAccompanimentList: musicSheetType[] musicSheetSoundList: musicSheetType[] }) { if (!beatState.times.length) return try { if (musicSheetSoundList.length + musicSheetAccompanimentList.length > 0) { // 扁平化数据 生成任务队列 const taskAudio: taskAudioType = [] ;[...musicSheetSoundList, ...musicSheetAccompanimentList].map((item) => { taskAudio.push({ obj: item, type: 'audioFileUrl' }) item.solmizationFileUrl && // 有唱名加上唱名 taskAudio.push({ obj: item, type: 'solmizationFileUrl' }) }) beatState.totalIndex.value = taskAudio.length /* 加载节拍器 */ if (!tickMp3Buff || !tockMp3Buff) { const [tickMp3Bf, tockMp3Bf] = await crunker.fetchAudio(tickMp3, tockMp3) tickMp3Buff = tickMp3Bf tockMp3Buff = tockMp3Bf } /* 加上所有的音频文件 */ await Promise.all( taskAudio.map(async (item) => { const [audioBuff] = await crunker.fetchAudio(item.obj[item.type]!) item.audioBuff = audioBuff }) ) /* 异步上传 */ await new Promise((res) => { /* 合成音源 */ taskAudio.map(async (item) => { const audioBlob = mergeBeats(item.audioBuff!) const url = await uploadFile(audioBlob) item.obj[item.type == 'audioFileUrl' ? 'audioBeatMixUrl' : 'solmizationBeatUrl'] = url beatState.currentIndex.value++ if (beatState.currentIndex.value >= beatState.totalIndex.value) { res(null) } }) }) } } catch (err) { console.log('处理音频合成上传失败', err) } // 清空数据 beatState.currentIndex.value = 0 beatState.totalIndex.value = 0 beatState.times = [] } // 根据buffer合成音源返回blob function mergeBeats(audioBuff: AudioBuffer) { // 计算音频空白时间 const silenceDuration = crunker.calculateSilenceDuration(audioBuff) const beats: AudioBuffer[] = [] const currentTimes: number[] = [] beatState.times.map((items) => { items.map((time, index) => { beats.push(index === 0 ? tickMp3Buff! : tockMp3Buff!) currentTimes.push(time + silenceDuration) }) }) //合并 const mergeAudioBuff = crunker.mergeAudioBuffers([audioBuff, ...beats], [0, ...currentTimes]) //转为 blob return crunker.audioBuffToBlob(mergeAudioBuff) } /** * 上传文件 */ async function uploadFile(audioBlob: Blob) { const filename = `${new Date().getTime()}.mp3` const { data } = await getUploadSign({ filename, bucketName: 'cloud-coach', postData: { filename, acl: 'public-read', key: filename, unknowValueField: [] } }) const url = await onOnlyFileUpload('', { KSSAccessKeyId: data.KSSAccessKeyId, acl: 'public-read', file: audioBlob, key: filename, name: filename, policy: data.policy, signature: data.signature }) return url }