UNPKG

@volley/recognition-client-sdk

Version:

Recognition Service TypeScript/Node.js Client SDK

170 lines (147 loc) 4.24 kB
/** * Audio Ring Buffer * Manages circular buffer for audio data with overflow detection */ export interface BufferedAudio { data: ArrayBuffer | ArrayBufferView; timestamp: number; } export interface AudioRingBufferConfig { maxBufferDurationSec: number; chunksPerSecond: number; logger?: (level: 'debug' | 'info' | 'warn' | 'error', message: string, data?: any) => void; } export class AudioRingBuffer { private buffer: BufferedAudio[] = []; private bufferSize: number; private writeIndex = 0; private readIndex = 0; private hasWrapped = false; private totalBufferedBytes = 0; private overflowCount = 0; private chunksBuffered = 0; private logger?: (level: 'debug' | 'info' | 'warn' | 'error', message: string, data?: any) => void; constructor(config: AudioRingBufferConfig) { this.bufferSize = config.maxBufferDurationSec * config.chunksPerSecond; this.buffer = new Array(this.bufferSize); if (config.logger) { this.logger = config.logger; } } /** * Write audio chunk to ring buffer with overflow detection */ write(audioData: ArrayBuffer | ArrayBufferView): void { const bytes = ArrayBuffer.isView(audioData) ? audioData.byteLength : audioData.byteLength; // Write to current position this.buffer[this.writeIndex] = { data: audioData, timestamp: Date.now() }; // Advance write pointer const nextWriteIndex = (this.writeIndex + 1) % this.bufferSize; // Detect overflow: write caught up to read if (nextWriteIndex === this.readIndex && this.writeIndex !== this.readIndex) { this.hasWrapped = true; this.overflowCount++; // Log buffer overflow event if (this.logger) { this.logger('debug', 'Buffer overflow detected', { bufferSize: this.bufferSize, totalOverflows: this.overflowCount, droppedChunk: this.buffer[this.readIndex]?.timestamp }); } // Move read pointer forward to make room (drop oldest) this.readIndex = (this.readIndex + 1) % this.bufferSize; } this.writeIndex = nextWriteIndex; this.chunksBuffered++; this.totalBufferedBytes += bytes; } /** * Read next chunk from buffer */ read(): BufferedAudio | null { if (this.isEmpty()) { return null; } const chunk = this.buffer[this.readIndex]; this.readIndex = (this.readIndex + 1) % this.bufferSize; return chunk || null; } /** * Read all buffered chunks without removing them */ readAll(): BufferedAudio[] { const chunks: BufferedAudio[] = []; let index = this.readIndex; while (index !== this.writeIndex) { const chunk = this.buffer[index]; if (chunk) { chunks.push(chunk); } index = (index + 1) % this.bufferSize; } return chunks; } /** * Flush all buffered data and advance read pointer */ flush(): BufferedAudio[] { const chunks = this.readAll(); this.readIndex = this.writeIndex; return chunks; } /** * Get count of buffered chunks */ getBufferedCount(): number { if (this.writeIndex >= this.readIndex) { return this.writeIndex - this.readIndex; } else { // Wrapped around return this.bufferSize - this.readIndex + this.writeIndex; } } /** * Check if buffer is empty */ isEmpty(): boolean { return this.readIndex === this.writeIndex; } /** * Check if buffer has overflowed */ isOverflowing(): boolean { return this.hasWrapped; } /** * Clear the buffer and reset all counters * Frees memory by releasing all stored audio chunks */ clear(): void { this.buffer = []; this.writeIndex = 0; this.readIndex = 0; this.hasWrapped = false; this.overflowCount = 0; this.chunksBuffered = 0; this.totalBufferedBytes = 0; if (this.logger) { this.logger('debug', 'Audio buffer cleared'); } } /** * Get buffer statistics */ getStats() { return { chunksBuffered: this.chunksBuffered, currentBufferedChunks: this.getBufferedCount(), overflowCount: this.overflowCount, hasWrapped: this.hasWrapped, totalBufferedBytes: this.totalBufferedBytes }; } }