| export function decodeBase64Audio( |
| base64String: string, |
| mimeType = 'audio/mpeg', |
| sampleRate = 44100, |
| numChannels = 1 |
| ): string { |
| |
| const byteString = atob(base64String) |
| const byteArray = new Uint8Array(byteString.length) |
|
|
| for (let i = 0; i < byteString.length; i += 1) { |
| byteArray[i] = byteString.charCodeAt(i) |
| } |
|
|
| let blob: Blob |
|
|
| if (mimeType === 'audio/pcm16') { |
| |
| const wavHeader = createWavHeader(byteArray.length, sampleRate, numChannels) |
| const wavData = new Uint8Array(wavHeader.length + byteArray.length) |
| wavData.set(wavHeader, 0) |
| wavData.set(byteArray, wavHeader.length) |
|
|
| blob = new Blob([wavData], { type: 'audio/wav' }) |
| } else { |
| blob = new Blob([byteArray], { type: mimeType }) |
| } |
|
|
| return URL.createObjectURL(blob) |
| } |
|
|
| |
| function createWavHeader( |
| dataLength: number, |
| sampleRate: number, |
| numChannels: number |
| ): Uint8Array { |
| const header = new ArrayBuffer(44) |
| const view = new DataView(header) |
|
|
| const blockAlign = numChannels * 2 |
| const byteRate = sampleRate * blockAlign |
|
|
| |
| view.setUint32(0, 0x52494646, false) |
| view.setUint32(4, 36 + dataLength, true) |
| view.setUint32(8, 0x57415645, false) |
|
|
| |
| view.setUint32(12, 0x666d7420, false) |
| view.setUint32(16, 16, true) |
| view.setUint16(20, 1, true) |
| view.setUint16(22, numChannels, true) |
| view.setUint32(24, sampleRate, true) |
| view.setUint32(28, byteRate, true) |
| view.setUint16(32, blockAlign, true) |
| view.setUint16(34, 16, true) |
|
|
| |
| view.setUint32(36, 0x64617461, false) |
| view.setUint32(40, dataLength, true) |
|
|
| return new Uint8Array(header) |
| } |
|
|