# Audio Recording Pipeline Architecture in OpenWhispr: 6-Stage Technical Breakdown

> Explore OpenWhispr's 6-stage audio recording pipeline architecture. Discover how it achieves low-latency speech-to-text with pre-roll buffering and real-time preview.

- Repository: [OpenWhispr/openwhispr](https://github.com/OpenWhispr/openwhispr)
- Tags: architecture
- Published: 2026-09-06

---

**OpenWhispr implements a six-stage audio recording pipeline architecture that spans the Electron renderer process, main-process bridge, and specialized helper modules to deliver low-latency speech-to-text with optional pre-roll buffering and real-time preview capabilities.**

OpenWhispr is an open-source voice dictation application built on Electron that engineers a sophisticated audio recording pipeline architecture to handle everything from global hot-key activation to provider-specific transcription routing. The system is designed to minimize latency through microphone warm-up guards, optional pre-roll capture, and a dual-mode provider system supporting both batch and streaming transcription services. Understanding this architecture is essential for developers integrating custom providers or optimizing capture performance across different hardware configurations.

## The Six Stages of the Audio Recording Pipeline

The pipeline flows through six distinct logical stages, each handled by specific modules in `src/helpers/`.

### Stage 1: Hot-Key Activation and Policy Check

When a user presses the global dictation hot-key, the [`hotkeyManager.js`](https://github.com/OpenWhispr/openwhispr/blob/main/hotkeyManager.js) module triggers the renderer process to instantiate an `AudioManager`. Before capture begins, the system validates recording policies through `isRecordingAllowedByPolicy` (line 90) and checks for active recording conflicts.

### Stage 2: Microphone Acquisition and Warm-Up

The `AudioManager` resolves the preferred input device via `resolvePreferredMicrophone()` and builds audio constraints in `getAudioConstraints()` (lines 29-43) that explicitly disable automatic gain control (AGC), echo cancellation, and noise suppression to minimize latency. The `_warmMicDriverIfCold()` method (lines 127-158) pre-opens the audio driver, while a mic-hold guard maintains the device connection between recordings to avoid cold-start delays.

### Stage 3: Pre-Roll Capture (Optional)

For push-to-talk workflows, `prepareMicCapture()` initiates a `MediaRecorder` that buffers the previous 2 seconds of audio via `_startPreRollRecorder()` (lines 135-144). This pre-roll segment is later merged with the main recording to prevent truncation of speech that began before the activation key was pressed.

### Stage 4: Main Recording and Preview Streaming

The `startRecording()` method (lines 200-254) establishes the primary capture stream. For batch processing, it creates a batch recorder; for real-time workflows, it initializes an `AudioWorkletNode` using `getWorkletBlobUrl()` (lines 98-106) to stream PCM chunks via `_previewProcessor` (lines 131-143) through `window.electronAPI?.sendDictationPreviewAudio` for live transcription previews without waiting for the full upload.

### Stage 5: Transcription and Reasoning Routing

Upon recording completion, `mergeRecordedSegments()` (lines 555-568) combines pre-roll and main audio segments into a single Blob. The system routes to either **batch** providers (Tinfoil, Mistral, Gemini via `PROXY_TRANSCRIPTION_PROVIDERS`, lines 94-106) or **streaming** providers (Deepgram, AssemblyAI, OpenAI-Realtime via `STREAMING_PROVIDERS`, lines 52-91). The `resolveReasoningRoute` function (lines 86-138) then determines post-processing scope (cleanup, agent routing, or translation).

### Stage 6: Cleanup and Analytics

The `setMicCaptureStatus()` method (lines 226-234) releases the microphone through `micStreamHold.drop()` (line 99), though `micStreamHold.touch()` (line 104) may restart the idle hold timer if the mic is to remain warm. Analytics sync logic (`analyticsSyncEnabled`, lines 52-58) records word counts and retention policies before the pipeline resets.

## Key Architectural Components

### Microphone Warm-Up Guard

To prevent latency on slow machines, [`micWarmState.js`](https://github.com/OpenWhispr/openwhispr/blob/main/micWarmState.js) implements a warm-up guard that tracks the `_micWarmedAt` timestamp refreshed by `_stampMicWarm` (line 94). The `isMicWarm` check prevents redundant driver openings, ensuring sub-second activation for subsequent recordings.

### Batch vs. Streaming Provider Architecture

**Batch providers** proxy audio through main-process IPC handlers like `proxyTinfoilTranscription`, constructing payloads via `buildPayload()` for complete-file upload. **Streaming providers** maintain persistent WebSocket connections, exposing `warmup()`, `start()`, `send()`, and `onPartial()` callbacks that receive PCM chunks in real-time as the `AudioWorklet` captures them.

### Pre-Roll and Segment Merging

The system automatically prepends buffered pre-roll audio when `mergeRecordedSegments()` executes (lines 555-568), ensuring no speech is lost during the activation delay. This occurs via the `mergeAudioSegments` IPC call (lines 666-668) before transcription begins.

## Developer Integration Examples

### Initializing a Recording Session

```javascript
import { AudioManager } from "@/helpers/audioManager";

const audioMgr = new AudioManager();

audioMgr.setCallbacks({
  onStateChange: (state) => console.log("State:", state),
  onTranscriptionComplete: (result) => console.log("Transcript:", result),
  onPartialTranscript: (partial) => console.log("Partial:", partial),
});

await audioMgr.startRecording();   // Lines 200-254

```

### Configuring a Streaming Provider

```javascript
const streaming = audioMgr.getStreamingProvider();
await streaming.warmup({ model: "openai-realtime", language: "en" });
await streaming.start({ model: "openai-realtime", language: "en" });

audioMgr._previewProcessor.port.onmessage = (ev) => {
  if (ev.data instanceof ArrayBuffer) streaming.send(ev.data);
};

```

### Merging Pre-Roll with Main Recording

```javascript
const mergedBlob = await audioMgr.mergeRecordedSegments(audioMgr._batchSegments);
// mergedBlob ready for batch provider upload

```

## Core Source Files

- [`src/helpers/audioManager.js`](https://github.com/OpenWhispr/openwhispr/blob/main/src/helpers/audioManager.js) — Central orchestrator handling microphone acquisition, pre-roll management, and provider routing.
- [`src/helpers/micWarmState.js`](https://github.com/OpenWhispr/openwhispr/blob/main/src/helpers/micWarmState.js) — Warm-up guard implementation preventing redundant driver initialization.
- [`src/helpers/activeMicRecovery.js`](https://github.com/OpenWhispr/openwhispr/blob/main/src/helpers/activeMicRecovery.js) — Dead microphone detection and recovery logic.
- [`src/helpers/ipcHandlers.js`](https://github.com/OpenWhispr/openwhispr/blob/main/src/helpers/ipcHandlers.js) — Main-process endpoints for transcription proxies and preview audio streaming.
- [`src/helpers/hotkeyManager.js`](https://github.com/OpenWhispr/openwhispr/blob/main/src/helpers/hotkeyManager.js) — Global hot-key registration and dispatch.

## Summary

- OpenWhispr's **audio recording pipeline architecture** consists of six stages: hot-key activation, microphone warm-up, pre-roll capture, main recording with optional preview, transcription routing, and cleanup.
- The system optimizes latency through **microphone warm-up guards** in [`micWarmState.js`](https://github.com/OpenWhispr/openwhispr/blob/main/micWarmState.js) and **mic-hold patterns** that keep drivers open between sessions.
- **Dual-mode provider support** enables both batch uploads (via proxy IPC) and real-time streaming (via WebSocket PCM chunks) through the `AudioManager` abstraction.
- **Pre-roll buffering** captures 2 seconds of audio prior to activation, merged transparently with the main recording to prevent speech truncation.
- **AudioWorklet-based preview** streams PCM data to the main process for instantaneous partial transcription feedback without waiting for recording completion.

## Frequently Asked Questions

### How does OpenWhispr minimize latency when starting a recording?

The pipeline implements a **microphone warm-up guard** through [`micWarmState.js`](https://github.com/OpenWhispr/openwhispr/blob/main/micWarmState.js) that tracks when the audio driver was last opened via `_stampMicWarm`. Rather than closing the microphone immediately after recording, the `micStreamHold` mechanism maintains the device connection for a configurable idle period, allowing subsequent activations to bypass expensive driver initialization delays. Additionally, `_warmMicDriverIfCold()` (lines 127-158) proactively opens the driver when the application predicts imminent use.

### What is the difference between batch and streaming transcription providers?

**Batch providers** (Tinfoil, Mistral, Gemini) defined in `PROXY_TRANSCRIPTION_PROVIDERS` (lines 94-106) collect the entire audio Blob through `mergeRecordedSegments()` before sending it via IPC to the main process for HTTP upload. **Streaming providers** (Deepgram, AssemblyAI, OpenAI-Realtime) defined in `STREAMING_PROVIDERS` (lines 52-91) establish persistent connections during `startRecording()` and receive PCM chunks in real-time through the `_previewProcessor` AudioWorklet, enabling word-by-word transcription without waiting for the user to stop speaking.

### How does the pre-roll buffer prevent missing the beginning of speech?

When `prepareMicCapture()` is called, `_startPreRollRecorder()` (lines 135-144) initiates a continuous 2-second rolling buffer using `MediaRecorder`. Upon hot-key activation, this buffered segment is retained while the main recording begins. When transcription is requested, `mergeRecordedSegments()` (lines 555-568) concatenates the pre-roll Blob with the main recording segments, ensuring that words spoken during the physical reaction time between thought and keypress are preserved in the final transcript.

### Can developers intercept raw PCM data for custom processing?

Yes. The `AudioManager` exposes the `_previewProcessor` AudioWorklet node (initialized via `getWorkletBlobUrl()`, lines 98-106) which posts PCM chunks through its message port. Developers can attach listeners to `audioMgr._previewProcessor.port.onmessage` to intercept `ArrayBuffer` audio data before it reaches transcription providers, enabling custom filters, visualizations, or alternative routing pipelines.