Files
ai-app-skr/utils/audioUtils.ts
2025-11-21 00:24:18 +08:00

81 lines
2.6 KiB
TypeScript

export const base64ToUint8Array = (base64: string) => {
const binaryString = atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
};
export const uint8ArrayToBase64 = (bytes: Uint8Array) => {
let binary = '';
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
};
export const triggerDownload = (blob: Blob, filename: string) => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
export const createWavFileFromPcm = (pcmData: Uint8Array, sampleRate: number = 24000, numChannels: number = 1): Blob => {
const header = new ArrayBuffer(44);
const view = new DataView(header);
const writeString = (view: DataView, offset: number, string: string) => {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
};
writeString(view, 0, 'RIFF');
view.setUint32(4, 36 + pcmData.length, true);
writeString(view, 8, 'WAVE');
writeString(view, 12, 'fmt ');
view.setUint32(16, 16, true);
view.setUint16(20, 1, true);
view.setUint16(22, numChannels, true);
view.setUint32(24, sampleRate, true);
view.setUint32(28, sampleRate * numChannels * 2, true);
view.setUint16(32, numChannels * 2, true);
view.setUint16(34, 16, true);
writeString(view, 36, 'data');
view.setUint32(40, pcmData.length, true);
return new Blob([view, pcmData], { type: 'audio/wav' });
};
export const processAndDownloadAudio = (base64Data: string, filename: string) => {
try {
// Check for RIFF header (WAV)
// Some base64 strings might have newlines, strip them if necessary,
// but generally atob handles it or we assume clean base64.
const binaryString = atob(base64Data.substring(0, 50).replace(/\s/g, ''));
const isWav = binaryString.startsWith('RIFF');
if (isWav) {
const bytes = base64ToUint8Array(base64Data);
const blob = new Blob([bytes], { type: 'audio/wav' });
triggerDownload(blob, filename);
} else {
// Assume Raw PCM 24kHz 16-bit Mono (Gemini Flash TTS default)
const bytes = base64ToUint8Array(base64Data);
const blob = createWavFileFromPcm(bytes, 24000, 1);
triggerDownload(blob, filename);
}
} catch (e) {
console.error("Error downloading audio", e);
}
};