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); } };