UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

108 lines (107 loc) • 4.57 kB
import { c as pcmToMulaw, o as createStreamingPcmResampler, s as mulawToPcm } from "./audio-energy-fMmNST7n.mjs"; import "./realtime-voice-provider-D_Fp61Jy.mjs"; //#region extensions/openai/realtime-quicksilver-audio-buffer.ts const RELAY_FRAME_SAMPLES = 480; const MAX_PENDING_RELAY_FRAMES = 250; const OPENAI_QUICKSILVER_AUDIO_FRAME_DURATION_MS = 20; const OPENAI_QUICKSILVER_RELAY_FRAME_BYTES = RELAY_FRAME_SAMPLES * 2; const OPENAI_QUICKSILVER_MAX_PENDING_AUDIO_BYTES = 960 * MAX_PENDING_RELAY_FRAMES; function assertOpenAIQuicksilverPcmOutput(format) { if (format && (format.encoding !== "pcm16" || format.sampleRateHz !== 24e3 || format.channels !== 1)) throw new Error("GPT-Live direct audio output requires mono PCM16 at 24 kHz"); } /** Keeps telephony resampling state and its delayed output tail with the audio adapter. */ var OpenAIQuicksilverAudioAdapter = class { constructor(config) { this.config = config; this.inbound = createStreamingPcmResampler(8e3, 24e3); this.outbound = createStreamingPcmResampler(24e3, 8e3); this.telephony = config.audioFormat?.encoding === "g711_ulaw"; } decodeInput(audio) { return this.telephony ? this.inbound.process(mulawToPcm(audio)) : audio; } sendOutput(pcm) { const audio = this.telephony ? pcmToMulaw(this.outbound.process(pcm)) : pcm; if (audio.length > 0) this.config.onAudio(audio); } finishOutput() { if (!this.telephony) return; const tail = pcmToMulaw(this.outbound.flush()); this.outbound = createStreamingPcmResampler(24e3, 8e3); if (tail.length > 0) this.config.onAudio(tail); } reset() { this.inbound = createStreamingPcmResampler(8e3, 24e3); this.outbound = createStreamingPcmResampler(24e3, 8e3); } }; /** One real-time clock for WebSocket PCM and WebRTC RTP; stalls never drain capture in a burst. */ var OpenAIQuicksilverAudioClock = class { constructor(onFrame) { this.onFrame = onFrame; } start() { if (this.timer) return; let nextFrameAt = performance.now(); const tick = () => { const skippedFrames = Math.max(0, Math.floor((performance.now() - nextFrameAt) / 20)); nextFrameAt += (skippedFrames + 1) * 20; this.timer = setTimeout(tick, Math.max(0, nextFrameAt - performance.now())); this.timer.unref?.(); this.onFrame(skippedFrames); }; tick(); } stop() { if (this.timer) { clearTimeout(this.timer); this.timer = void 0; } } }; var OpenAIQuicksilverPendingAudio = class { constructor(maxBytes = OPENAI_QUICKSILVER_MAX_PENDING_AUDIO_BYTES, sampleBytes = 2) { this.maxBytes = maxBytes; this.sampleBytes = sampleBytes; this.readOffset = 0; this.pendingBytes = 0; } get length() { return this.pendingBytes; } append(incoming) { const alignedLength = incoming.length - incoming.length % this.sampleBytes; if (alignedLength === 0) return; const retainedBytes = Math.min(alignedLength, this.maxBytes); const sourceOffset = alignedLength - retainedBytes; const storage = this.storage ??= Buffer.alloc(this.maxBytes); const droppedBytes = Math.max(0, this.pendingBytes + retainedBytes - this.maxBytes); this.readOffset = (this.readOffset + droppedBytes) % this.maxBytes; this.pendingBytes -= droppedBytes; const writeOffset = (this.readOffset + this.pendingBytes) % this.maxBytes; const firstBytes = Math.min(retainedBytes, this.maxBytes - writeOffset); incoming.copy(storage, writeOffset, sourceOffset, sourceOffset + firstBytes); if (firstBytes < retainedBytes) incoming.copy(storage, 0, sourceOffset + firstBytes, sourceOffset + retainedBytes); this.pendingBytes += retainedBytes; } readInto(target) { const alignedLength = target.length - target.length % this.sampleBytes; const readBytes = Math.min(alignedLength, this.pendingBytes); const storage = this.storage; if (readBytes === 0 || !storage) return 0; const firstBytes = Math.min(readBytes, this.maxBytes - this.readOffset); storage.copy(target, 0, this.readOffset, this.readOffset + firstBytes); if (firstBytes < readBytes) storage.copy(target, firstBytes, 0, readBytes - firstBytes); this.readOffset = (this.readOffset + readBytes) % this.maxBytes; this.pendingBytes -= readBytes; if (this.pendingBytes === 0) this.readOffset = 0; return readBytes; } clear() { this.storage = void 0; this.readOffset = 0; this.pendingBytes = 0; } }; //#endregion export { OpenAIQuicksilverPendingAudio as a, OpenAIQuicksilverAudioClock as i, OPENAI_QUICKSILVER_RELAY_FRAME_BYTES as n, assertOpenAIQuicksilverPcmOutput as o, OpenAIQuicksilverAudioAdapter as r, OPENAI_QUICKSILVER_AUDIO_FRAME_DURATION_MS as t };