UNPKG

murmuraba

Version:

Real-time audio noise reduction with advanced chunked processing for web applications

114 lines (113 loc) 3.95 kB
/** * Voice Segment Detection * Identifies continuous voice segments from VAD scores */ export class SegmentDetector { constructor(minSegmentDuration = 0.1, // 100ms hangoverTime = 0.3, // 300ms mergeGap = 0.5 // 500ms ) { this.minSegmentDuration = minSegmentDuration; this.hangoverTime = hangoverTime; this.mergeGap = mergeGap; } /** * Detect voice segments from VAD scores */ detectSegments(vadScores, frameTime, threshold = 0.5) { const segments = []; let currentSegment = null; let hangoverFrames = Math.ceil(this.hangoverTime / frameTime); let hangoverCount = 0; for (let i = 0; i < vadScores.length; i++) { const timestamp = i * frameTime; const vadScore = vadScores[i]; const isVoice = vadScore > threshold; if (isVoice) { if (!currentSegment) { // Start new segment currentSegment = { startTime: timestamp, endTime: timestamp, confidence: vadScore }; } else { // Extend current segment currentSegment.endTime = timestamp; currentSegment.confidence = (currentSegment.confidence + vadScore) / 2; } hangoverCount = hangoverFrames; } else if (currentSegment && hangoverCount > 0) { // In hangover period currentSegment.endTime = timestamp; hangoverCount--; } else if (currentSegment) { // End segment const duration = currentSegment.endTime - currentSegment.startTime; if (duration >= this.minSegmentDuration) { segments.push(currentSegment); } currentSegment = null; hangoverCount = 0; } } // Handle last segment if (currentSegment) { const duration = currentSegment.endTime - currentSegment.startTime; if (duration >= this.minSegmentDuration) { segments.push(currentSegment); } } // Merge close segments return this.mergeSegments(segments); } /** * Merge segments that are close together */ mergeSegments(segments) { if (segments.length < 2) return segments; const merged = []; let current = segments[0]; for (let i = 1; i < segments.length; i++) { const next = segments[i]; const gap = next.startTime - current.endTime; if (gap <= this.mergeGap) { // Merge segments current = { startTime: current.startTime, endTime: next.endTime, confidence: (current.confidence + next.confidence) / 2 }; } else { // Keep separate merged.push(current); current = next; } } merged.push(current); return merged; } /** * Apply median filter to smooth VAD scores */ smoothScores(vadScores, windowSize = 5) { const smoothed = []; const halfWindow = Math.floor(windowSize / 2); for (let i = 0; i < vadScores.length; i++) { const start = Math.max(0, i - halfWindow); const end = Math.min(vadScores.length, i + halfWindow + 1); const window = vadScores.slice(start, end); // Calculate median window.sort((a, b) => a - b); const median = window[Math.floor(window.length / 2)]; smoothed.push(median); } return smoothed; } }