// Convert Float32Array (Web Audio API) to PCM Int16 (Gemini API requirement) export function floatTo16BitPCM(float32Array: Float32Array): ArrayBuffer { const buffer = new ArrayBuffer(float32Array.length * 2); const view = new DataView(buffer); let offset = 0; for (let i = 0; i < float32Array.length; i++) { let s = Math.max(-1, Math.min(1, float32Array[i])); s = s < 0 ? s * 0x8000 : s * 0x7FFF; view.setInt16(offset, s, true); // Little Endian offset += 2; } return buffer; } // Manual encoding helper as per guidelines export function encodeBase64(bytes: Uint8Array): string { let binary = ''; const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } // Manual decoding helper as per guidelines export function decodeBase64(base64: string): Uint8Array { 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; } // Decode raw PCM data from Gemini to AudioBuffer export async function decodeAudioData( data: Uint8Array, ctx: AudioContext, sampleRate: number = 24000, numChannels: number = 1, ): Promise { // Ensure the byte length is even for Int16Array if (data.byteLength % 2 !== 0) { data = data.subarray(0, data.byteLength - 1); } const dataInt16 = new Int16Array(data.buffer, data.byteOffset, data.byteLength / 2); const buffer = ctx.createBuffer(numChannels, dataInt16.length / numChannels, sampleRate); for (let channel = 0; channel < numChannels; channel++) { const channelData = buffer.getChannelData(channel); for (let i = 0; i < dataInt16.length; i++) { channelData[i] = dataInt16[i] / 32768.0; } } return buffer; }