Socket
Book a DemoInstallSign in
Socket

murmuraba

Package Overview
Dependencies
Maintainers
0
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

murmuraba

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

3.0.3
latest
Source
npmnpm
Version published
Weekly downloads
22
-8.33%
Maintainers
0
Weekly downloads
 
Created
Source

🎵 Murmuraba - Real-time Neural Noise Reduction with Chunked Streaming

npm version React TypeScript License: MIT

Professional-grade real-time audio streaming with neural noise reduction using RNNoise WASM. Process long recordings efficiently with automatic chunking - no need to wait for recording completion. Now with advanced Voice Activity Detection (VAD) capabilities!

🚀 Why Murmuraba?

Traditional audio recording libraries make you wait until the user stops recording to process the entire audio file. This approach fails for:

  • Long recordings (podcasts, meetings, interviews)
  • Real-time transcription needs
  • Live streaming applications
  • Memory-constrained environments

Murmuraba solves this with automatic chunked streaming: Audio is processed in real-time segments while recording continues, giving you instant access to processed chunks with neural noise reduction applied.

📦 Installation

npm install murmuraba

🎯 Core Concept: The Hook-First Approach

Murmuraba is built around the powerful useMurmubaraEngine hook that handles everything:

import { useMurmubaraEngine } from 'murmuraba';

function YourAudioApp() {
  const {
    recordingState,      // Real-time state with chunks array
    startRecording,      // Start chunked recording
    stopRecording,       // Stop and cleanup
    // ... more controls
  } = useMurmubaraEngine();
  
  // Your custom UI here
}

🔄 The Chunked Streaming Flow

How It Works

Microphone Input (Continuous Stream)
         ↓
    [8 seconds] → Chunk 1 → Process → Noise Reduced → Available Immediately
         ↓
    [8 seconds] → Chunk 2 → Process → Noise Reduced → Available Immediately
         ↓
    [8 seconds] → Chunk 3 → Process → Noise Reduced → Available Immediately
         ↓
    ... continues until stop

Each chunk is processed independently with:

  • Neural noise reduction via RNNoise
  • Dual audio URLs (original + processed)
  • Real-time metrics (VAD, noise levels)
  • Instant availability for playback/export/upload

Basic Implementation

import { useMurmubaraEngine } from 'murmuraba';
import { useEffect } from 'react';

function StreamingRecorder() {
  const {
    recordingState,
    startRecording,
    stopRecording,
    exportChunkAsWav,
  } = useMurmubaraEngine({
    defaultChunkDuration: 8  // 8-second chunks
  });

  // Watch for new chunks in real-time
  useEffect(() => {
    const latestChunk = recordingState.chunks[recordingState.chunks.length - 1];
    
    if (latestChunk) {
      console.log('New chunk available!', {
        id: latestChunk.id,
        duration: latestChunk.duration,
        processedUrl: latestChunk.processedAudioUrl,  // Noise-reduced audio
        originalUrl: latestChunk.originalAudioUrl,    // Raw microphone input
        noiseReduction: latestChunk.noiseRemoved      // Percentage reduced
      });
      
      // You can immediately:
      // - Play it back
      // - Send to server
      // - Transcribe it
      // - Export it
      // No need to wait for recording to finish!
    }
  }, [recordingState.chunks.length]);

  return (
    <div>
      <button onClick={() => startRecording(10)}>
        Start Recording (10s chunks)
      </button>
      <button onClick={stopRecording}>Stop</button>
      
      {/* Real-time chunk list */}
      {recordingState.chunks.map(chunk => (
        <div key={chunk.id}>
          Chunk {chunk.index}: {chunk.duration}ms
          <audio src={chunk.processedAudioUrl} controls />
          <button onClick={() => exportChunkAsWav(chunk.id)}>
            Export WAV
          </button>
        </div>
      ))}
    </div>
  );
}

🧠 Neural Noise Reduction with RNNoise

Every audio chunk passes through RNNoise, a recurrent neural network trained on thousands of hours of speech data. This happens in real-time using WebAssembly for native performance.

The Processing Pipeline

// What happens inside each chunk:
Raw AudioRNNoise Neural NetworkClean Audio
           ↓
    - Removes background noise
    - Preserves voice clarity  
    - Maintains natural sound
    - ~85% noise reduction

Comparing Original vs Processed

function NoiseComparison() {
  const { recordingState, toggleChunkPlayback } = useMurmubaraEngine();
  
  return (
    <div>
      {recordingState.chunks.map(chunk => (
        <div key={chunk.id}>
          {/* Play original (noisy) */}
          <button onClick={() => toggleChunkPlayback(chunk.id, 'original')}>
            Play Original
          </button>
          
          {/* Play processed (clean) */}
          <button onClick={() => toggleChunkPlayback(chunk.id, 'processed')}>
            Play Processed
          </button>
          
          <span>Noise Reduced: {chunk.noiseRemoved}%</span>
        </div>
      ))}
    </div>
  );
}

📊 Real-time Chunk Data Structure

Each chunk in recordingState.chunks contains:

interface ProcessedChunk {
  // Identifiers
  id: string;                      // Unique chunk ID
  index: number;                   // Sequential index (0, 1, 2...)
  
  // Audio URLs (Blob URLs ready for immediate use)
  processedAudioUrl?: string;      // blob:http://... (noise-reduced)
  originalAudioUrl?: string;       // blob:http://... (raw input)
  
  // Timing
  duration: number;                // Duration in milliseconds
  startTime: number;               // Recording start timestamp
  endTime: number;                 // Recording end timestamp
  
  // Processing Metrics
  noiseRemoved: number;            // Noise reduction % (0-100)
  averageVad: number;              // Voice activity average (0-1)
  vadData: VadPoint[];             // Voice activity timeline
  
  // Quality Metrics
  metrics: {
    processingLatency: number;     // Processing time in ms
    frameCount: number;            // Audio frames processed
    inputLevel: number;            // Input volume (0-1)
    outputLevel: number;           // Output volume (0-1)
    noiseReductionLevel: number;  // Reduction applied (0-1)
  };
  
  // File Information
  originalSize: number;            // Original blob size in bytes
  processedSize: number;           // Processed blob size in bytes
  
  // State
  isPlaying: boolean;              // Currently playing
  isExpanded: boolean;             // UI expanded state
  isValid: boolean;                // Processing succeeded
  errorMessage?: string;           // Error details if failed
}

🎮 Complete Hook API

const {
  // 📊 State
  recordingState: {
    isRecording: boolean,          // Currently recording
    isPaused: boolean,              // Recording paused
    chunks: ProcessedChunk[],       // All processed chunks
    recordingTime: number,          // Total recording duration
    currentChunkTime: number,       // Current chunk progress
    playingChunks: Set<string>,     // Currently playing chunk IDs
    expandedChunk: string | null,   // Expanded chunk ID
  },
  
  // 🎙️ Recording Controls
  startRecording: (chunkDuration?: number) => Promise<void>,
  stopRecording: () => void,
  pauseRecording: () => void,
  resumeRecording: () => void,
  clearRecordings: () => void,
  
  // 🔊 Playback Controls
  toggleChunkPlayback: (chunkId: string, type?: 'processed' | 'original') => Promise<void>,
  stopAllPlayback: () => void,
  
  // 💾 Export Functions
  exportChunkAsWav: (chunkId: string, type?: 'processed' | 'original') => Promise<void>,
  exportChunkAsMp3: (chunkId: string, type?: 'processed' | 'original') => Promise<void>,
  downloadChunk: (chunkId: string, format: 'wav' | 'mp3', type?: 'processed' | 'original') => Promise<void>,
  exportAllChunks: () => Promise<void>,
  
  // 🎚️ Audio Controls
  inputGain: number,                 // Current gain (0.5-3.0)
  setInputGain: (gain: number) => void,
  agcEnabled: boolean,               // Auto gain control
  setAgcEnabled: (enabled: boolean) => Promise<void>,
  
  // 🔧 Engine Management
  isInitialized: boolean,
  isLoading: boolean,
  error: string | null,
  initialize: () => Promise<void>,
  reinitialize: () => Promise<void>,
  metrics: ProcessingMetrics | null,
  diagnostics: EngineDiagnostics | null,
  
} = useMurmubaraEngine(options);

🚀 Advanced Usage Patterns

Pattern 1: Auto-Upload Chunks to Server

function AutoUploadRecorder() {
  const { recordingState, startRecording } = useMurmubaraEngine();
  const uploadedRef = useRef(new Set());
  
  useEffect(() => {
    recordingState.chunks.forEach(async chunk => {
      if (!uploadedRef.current.has(chunk.id)) {
        uploadedRef.current.add(chunk.id);
        
        // Convert blob URL to blob
        const response = await fetch(chunk.processedAudioUrl!);
        const blob = await response.blob();
        
        // Upload to your server
        const formData = new FormData();
        formData.append('audio', blob, `chunk-${chunk.id}.wav`);
        formData.append('metadata', JSON.stringify({
          duration: chunk.duration,
          noiseReduction: chunk.noiseRemoved,
          vad: chunk.averageVad
        }));
        
        await fetch('/api/upload-chunk', {
          method: 'POST',
          body: formData
        });
        
        console.log(`Uploaded chunk ${chunk.id}`);
      }
    });
  }, [recordingState.chunks]);
  
  return <button onClick={() => startRecording(5)}>Start 5s Chunks</button>;
}

Pattern 2: Real-time Transcription

function LiveTranscription() {
  const { recordingState } = useMurmubaraEngine();
  const [transcripts, setTranscripts] = useState<Record<string, string>>({});
  
  useEffect(() => {
    const latestChunk = recordingState.chunks[recordingState.chunks.length - 1];
    
    if (latestChunk && !transcripts[latestChunk.id]) {
      // Send to transcription service
      transcribeChunk(latestChunk).then(text => {
        setTranscripts(prev => ({
          ...prev,
          [latestChunk.id]: text
        }));
      });
    }
  }, [recordingState.chunks.length]);
  
  return (
    <div>
      {recordingState.chunks.map(chunk => (
        <p key={chunk.id}>
          [{chunk.index}]: {transcripts[chunk.id] || 'Transcribing...'}
        </p>
      ))}
    </div>
  );
}

Pattern 3: Voice Activity Detection (VAD)

function VoiceDetector() {
  const { recordingState, metrics } = useMurmubaraEngine();
  
  return (
    <div>
      {/* Real-time VAD */}
      {metrics && (
        <div>
          Voice Active: {metrics.vadLevel > 0.5 ? '🎤 Speaking' : '🔇 Silent'}
          Level: {(metrics.vadLevel * 100).toFixed(0)}%
        </div>
      )}
      
      {/* Historical VAD per chunk */}
      {recordingState.chunks.map(chunk => (
        <div key={chunk.id}>
          Chunk {chunk.index}: {(chunk.averageVad * 100).toFixed(0)}% voice activity
        </div>
      ))}
    </div>
  );
}

Pattern 4: Advanced VAD Analysis (NEW in v3.0.3)

import { murmubaraVAD, extractAudioMetadata } from 'murmuraba';

function AdvancedVADAnalysis() {
  const analyzeAudio = async (audioBuffer: ArrayBuffer) => {
    // Get accurate audio metadata
    const metadata = extractAudioMetadata(audioBuffer);
    console.log(`Duration: ${metadata.duration}s, Format: ${metadata.format}`);
    
    // Perform detailed VAD analysis
    const vadResult = await murmubaraVAD(audioBuffer);
    console.log(`Voice Activity: ${(vadResult.average * 100).toFixed(1)}%`);
    console.log(`Voice Segments: ${vadResult.voiceSegments?.length || 0}`);
    
    // Analyze voice segments
    vadResult.voiceSegments?.forEach((segment, i) => {
      console.log(`Segment ${i + 1}: ${segment.startTime}s - ${segment.endTime}s (confidence: ${segment.confidence})`);
    });
    
    return vadResult;
  };
  
  return (
    <div>
      {/* Use with recorded chunks */}
      <button onClick={async () => {
        const response = await fetch(chunk.processedAudioUrl!);
        const arrayBuffer = await response.arrayBuffer();
        const analysis = await analyzeAudio(arrayBuffer);
        // Display results...
      }}>
        Analyze VAD
      </button>
    </div>
  );
}

🎯 New in v3.0.3: Advanced Voice Activity Detection

Murmuraba now includes powerful VAD analysis functions for detailed audio inspection:

murmubaraVAD(buffer: ArrayBuffer): Promise<VADResult>

Analyzes audio for voice activity using multiple algorithms:

  • Energy-based detection with adaptive noise floor
  • Zero-crossing rate analysis for voiced/unvoiced classification
  • RNNoise integration when available
  • Temporal smoothing for stable results

Returns:

{
  average: number;              // Average VAD score (0.0-1.0)
  scores: number[];             // Frame-by-frame VAD scores
  metrics: VADMetric[];         // Detailed metrics per frame
  voiceSegments: VoiceSegment[]; // Detected voice segments
}

extractAudioMetadata(buffer: ArrayBuffer): AudioMetadata

Extracts accurate metadata from audio files:

  • Supports WAV, MP3, WebM/Opus formats
  • Returns precise duration, sample rate, channels, bit depth
  • No more guessing audio duration!

Example:

const metadata = extractAudioMetadata(audioBuffer);
console.log(`Duration: ${metadata.duration}s`);
console.log(`Format: ${metadata.format}`);
console.log(`Sample Rate: ${metadata.sampleRate}Hz`);

⚙️ Configuration Options

interface UseMurmubaraEngineOptions {
  // Chunking
  defaultChunkDuration?: number;    // Seconds per chunk (default: 8)
  
  // Audio Processing
  bufferSize?: number;              // Audio buffer size (default: 16384)
  sampleRate?: number;              // Sample rate Hz (default: 48000)
  denoiseStrength?: number;         // Noise reduction 0-1 (default: 0.85)
  
  // Gain Control
  inputGain?: number;               // Initial gain 0.5-3.0 (default: 1.0)
  enableAGC?: boolean;              // Auto gain control (default: true)
  
  // Voice Detection
  spectralFloorDb?: number;         // Noise floor dB (default: -80)
  noiseFloorDb?: number;            // VAD threshold dB (default: -60)
  
  // Performance
  enableMetrics?: boolean;          // Real-time metrics (default: true)
  metricsUpdateInterval?: number;   // Update interval ms (default: 100)
  
  // Initialization
  autoInitialize?: boolean;         // Auto-init on mount (default: false)
  allowDegraded?: boolean;          // Allow fallback mode (default: true)
  
  // Debugging
  logLevel?: 'none' | 'error' | 'warn' | 'info' | 'debug';
}

🔧 Memory Management

Murmuraba automatically manages blob URLs to prevent memory leaks:

// URLs are automatically created and tracked
const processedUrl = URL.createObjectURL(processedBlob);  // ✅ Tracked

// URLs are automatically revoked when:
clearRecordings();  // All URLs revoked
// or on component unmount

📈 Performance Considerations

  • Chunk Duration: 5-10 seconds optimal for most use cases
  • Buffer Size: Larger = better quality, more latency
  • Sample Rate: 48kHz for quality, 16kHz for size
  • Memory: Each chunk ~100-500KB depending on duration

🎨 Building Custom UIs

Since you have full control via the hook, build any UI you want:

function MinimalRecorder() {
  const { recordingState, startRecording, stopRecording } = useMurmubaraEngine();
  
  if (!recordingState.isRecording) {
    return <button onClick={() => startRecording()}>🎤 Record</button>;
  }
  
  return (
    <div>
      <button onClick={stopRecording}>⏹ Stop</button>
      <div>
        Recording: {recordingState.recordingTime}s
        Chunks: {recordingState.chunks.length}
      </div>
    </div>
  );
}

🚨 Error Handling

function RobustRecorder() {
  const { 
    error, 
    isInitialized, 
    initialize, 
    startRecording 
  } = useMurmubaraEngine();
  
  const handleStart = async () => {
    try {
      if (!isInitialized) {
        await initialize();
      }
      await startRecording();
    } catch (err) {
      console.error('Recording failed:', err);
      // Handle specific errors
      if (err.message.includes('microphone')) {
        alert('Please allow microphone access');
      }
    }
  };
  
  if (error) {
    return <div>Error: {error}</div>;
  }
  
  return <button onClick={handleStart}>Start</button>;
}

🤝 Contributing

Contributions welcome! Please check our GitHub repository.

📄 License

MIT © Murmuraba Team

Built with ❤️ for developers who need professional audio streaming with neural noise reduction.

Keywords

audio

FAQs

Package last updated on 05 Aug 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

About

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.

  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc

U.S. Patent No. 12,346,443 & 12,314,394. Other pending.