Skip to content

Add microphone noise suppression options#777

Open
tiagogp wants to merge 8 commits into
webadderallorg:mainfrom
tiagogp:feat/audio-noise-suppression
Open

Add microphone noise suppression options#777
tiagogp wants to merge 8 commits into
webadderallorg:mainfrom
tiagogp:feat/audio-noise-suppression

Conversation

@tiagogp

@tiagogp tiagogp commented Jul 22, 2026

Copy link
Copy Markdown

Description

  • Add selectable microphone noise suppression modes: RNNoise, Speex, and disabled.
  • Process browser microphone streams through WASM suppressors with fallback handling.
  • Persist the selected mode through Electron preferences and expose it in the launch mic popover.
  • Add localized strings and focused unit coverage for normalization and fallback behavior.

Motivation

Creators need cleaner microphone audio without leaving Recordly. RNNoise is the default, Speex is available as a fallback, and users can disable processing when they want native/raw microphone behavior.

Type of Change

  • New Feature
  • Bug Fix
  • Refactor / Code Cleanup
  • Documentation Update
  • Other

Related Issue(s)

No linked issue.

Screenshots / Video

Not applicable.

Testing Guide

  • Run npx tsc --noEmit.
  • Run npm test.
  • Run npx vitest --run src/lib/audio/noiseSuppression.test.ts.
  • Run npx biome check src/lib/audio/noiseSuppression.ts src/lib/audio/noiseSuppressedStream.ts.

Checklist

  • I have performed a self-review of my code.
  • I have added any necessary screenshots or videos.
  • I have linked related issue(s) and updated the changelog if applicable.

Notes

  • The default mode is RNNoise, with Speex fallback when RNNoise initialization fails.
  • Noise suppression routes microphone capture through the browser microphone path so audio can be processed before recording.
  • Native macOS helper binaries are included as rebuilt artifacts from this branch.

Summary by CodeRabbit

  • New Features
    • Added microphone noise suppression controls with RNNoise, Speex, and Disabled options.
    • Noise suppression preferences are saved and restored between recordings.
    • Added automatic fallback when the selected mode is unavailable.
    • Added localized labels, descriptions, and availability warnings across supported languages.
  • Bug Fixes
    • Improved microphone processing reliability during native and browser-based recording.
    • Added graceful recovery when audio processing encounters errors.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds microphone noise suppression with RNNoise, Speex, and disabled modes across audio processing, recording persistence, native/browser capture, and launch UI. It also adds localized labels and rebuilds macOS native binaries.

Changes

Noise suppression recording flow

Layer / File(s) Summary
Noise suppression engines and fallback
src/lib/audio/noiseSuppression.ts, src/lib/audio/noiseSuppression.test.ts, package.json
Adds RNNoise and Speex WASM processors, disabled passthrough behavior, deterministic fallback selection, cleanup, normalization, and unit coverage.
AudioWorklet processing pipeline
src/lib/audio/noiseSuppressionWorklet.ts, src/lib/audio/noiseSuppressedStream.ts
Processes microphone frames in an AudioWorklet, reports initialization and processing events, exposes active-mode warnings, and manages stream and worklet cleanup.
Processed microphone stream integration
src/hooks/useScreenRecorder.ts
Loads and persists the selected mode, wraps raw microphone streams, surfaces localized warnings, cleans up processed streams, and routes audio through native and browser recording paths.
Preference persistence and launch controls
electron/..., src/components/launch/..., src/i18n/locales/*/launch.json
Extends recording preference contracts and IPC, adds MicPopover mode selection, exposes hook state, and adds localized labels and warnings.

macOS native binary rebuilds

Layer / File(s) Summary
Rebuilt macOS native executables
electron/native/bin/darwin-arm64/*, electron/native/bin/darwin-x64/*
Replaces cursor-monitor, ScreenCaptureKit, system-cursor, and window-list Mach-O artifacts with rebuilt binaries and updated embedded metadata.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant MicPopover
  participant useScreenRecorder
  participant getUserMedia
  participant NoiseSuppressedStream
  participant Recorder
  User->>MicPopover: Select noise suppression mode
  MicPopover->>useScreenRecorder: setNoiseSuppressionMode(mode)
  useScreenRecorder->>useScreenRecorder: Persist recording preference
  useScreenRecorder->>getUserMedia: Acquire raw microphone stream
  getUserMedia-->>useScreenRecorder: Return raw microphone stream
  useScreenRecorder->>NoiseSuppressedStream: Process microphone stream
  NoiseSuppressedStream-->>useScreenRecorder: Return processed microphone stream
  useScreenRecorder->>Recorder: Provide processed microphone audio
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the main addition of microphone noise suppression options.
Description check ✅ Passed The PR description follows the template and includes the required sections with clear motivation and testing guidance.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/lib/audio/noiseSuppressedStream.ts (1)

45-48: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Consider AudioWorkletNode instead of the deprecated ScriptProcessorNode.

ScriptProcessorNode is deprecated and runs the suppression callback on the main thread; AudioWorkletNode runs on the dedicated audio rendering thread and avoids the main-thread contention this file is already defending against with FRAME_BUDGET_MS/long-frame logging. Since this is new code, it may be worth building on the non-deprecated API instead of ScriptProcessorNode.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/audio/noiseSuppressedStream.ts` around lines 45 - 48, Replace the
deprecated ScriptProcessorNode created in the noise-suppressed stream setup with
an AudioWorkletNode-based implementation, moving the suppression processing into
an AudioWorklet processor while preserving the existing source, destination,
buffer, and frame-budget behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/useScreenRecorder.ts`:
- Around line 618-634: Update prepareMicrophoneStream to stop every track on the
acquired rawStream when createNoiseSuppressedMicrophoneStream throws, then
rethrow the error so both existing callers retain their current handling. Keep
successful processing and noiseSuppressedMicrophoneStreams registration
unchanged.

In `@src/lib/audio/noiseSuppressedStream.ts`:
- Around line 121-134: The reportWarnings function hardcodes English text before
it reaches the UI, bypassing localization. Change it to return a structured
warning containing an i18n key and any required parameters instead of a
formatted message, then update its caller in useScreenRecorder to translate that
warning before passing it to toast.warning while preserving the existing
disabled and fallback cases.

In `@src/lib/audio/noiseSuppression.ts`:
- Around line 62-81: Update processAudioFrame to retain samples that do not
complete an RNNoise frame across calls: buffer the trailing remainder, prepend
it to the next input frame, process every complete frame, and return output
aligned with the input without exposing unprocessed remainder samples. Preserve
the initialization check and native frameSize processing.

---

Nitpick comments:
In `@src/lib/audio/noiseSuppressedStream.ts`:
- Around line 45-48: Replace the deprecated ScriptProcessorNode created in the
noise-suppressed stream setup with an AudioWorkletNode-based implementation,
moving the suppression processing into an AudioWorklet processor while
preserving the existing source, destination, buffer, and frame-budget behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cf092073-337c-4d7e-ad2e-1179c72c5e2d

📥 Commits

Reviewing files that changed from the base of the PR and between 360b160 and 3302540.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (28)
  • electron/electron-env.d.ts
  • electron/ipc/register/settings.ts
  • electron/native/bin/darwin-arm64/recordly-native-cursor-monitor
  • electron/native/bin/darwin-arm64/recordly-screencapturekit-helper
  • electron/native/bin/darwin-arm64/recordly-system-cursors
  • electron/native/bin/darwin-arm64/recordly-window-list
  • electron/native/bin/darwin-x64/recordly-native-cursor-monitor
  • electron/native/bin/darwin-x64/recordly-screencapturekit-helper
  • electron/native/bin/darwin-x64/recordly-system-cursors
  • electron/native/bin/darwin-x64/recordly-window-list
  • electron/preload.ts
  • package.json
  • src/components/launch/LaunchWindow.tsx
  • src/components/launch/popovers/MicPopover.tsx
  • src/hooks/useScreenRecorder.ts
  • src/i18n/locales/en/launch.json
  • src/i18n/locales/es/launch.json
  • src/i18n/locales/fr/launch.json
  • src/i18n/locales/it/launch.json
  • src/i18n/locales/ko/launch.json
  • src/i18n/locales/nl/launch.json
  • src/i18n/locales/pt-BR/launch.json
  • src/i18n/locales/ru/launch.json
  • src/i18n/locales/zh-CN/launch.json
  • src/i18n/locales/zh-TW/launch.json
  • src/lib/audio/noiseSuppressedStream.ts
  • src/lib/audio/noiseSuppression.test.ts
  • src/lib/audio/noiseSuppression.ts

Comment thread src/hooks/useScreenRecorder.ts
Comment thread src/lib/audio/noiseSuppressedStream.ts
Comment thread src/lib/audio/noiseSuppression.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/lib/audio/noiseSuppressedStream.ts (1)

112-161: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share the worklet's outgoing message type instead of duck-typing event.data.

processor.port.onmessage here checks message.type against string literals with no static contract tying it to what noiseSuppressionWorklet.ts actually posts ("ready" | "error" | "processing-error" | "long-frame"). Exporting a union type for these outgoing messages from noiseSuppressionWorklet.ts (mirroring the existing NoiseSuppressionWorkletMessage for incoming messages) and typing event: MessageEvent<...> here would let the type checker catch contract drift between the two files.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/audio/noiseSuppressedStream.ts` around lines 112 - 161, Export a
union type for the worklet’s outgoing messages from noiseSuppressionWorklet.ts,
mirroring the existing NoiseSuppressionWorkletMessage definition and covering
ready, error, processing-error, and long-frame payloads. Import that type into
initializeNoiseSuppressionWorklet and type processor.port.onmessage as
MessageEvent of the shared outgoing-message union instead of treating event.data
as untyped data.
src/lib/audio/noiseSuppressionWorklet.ts (1)

98-139: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Reduce per-callback allocations in the real-time audio path.

process() calls this.outputBuffer.slice(readableLength) on essentially every render quantum (~every 2.7ms at 48kHz), and processBufferedFrame()/appendFloat32Arrays allocate a fresh combined Float32Array on every processed frame. This happens inside the same path that tracks a 12ms frameBudgetMs and logs long frames — repeated allocation/copy here adds GC pressure exactly where real-time budget is already tight, which can contribute to the very glitches the long-frame logging is meant to surface.

Consider replacing the append/slice pattern with a fixed-size ring buffer (head/tail indices into a pre-allocated Float32Array) so no new arrays are allocated during steady-state process()/processBufferedFrame() calls.

Also applies to: 144-182, 198-213

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/audio/noiseSuppressionWorklet.ts` around lines 98 - 139, Replace the
allocation-heavy output buffering used by process(), processBufferedFrame(), and
appendFloat32Arrays with a pre-allocated Float32Array ring buffer managed by
head/tail indices. Enqueue processed frame samples without creating combined
arrays, and dequeue samples directly into output without outputBuffer.slice();
preserve existing ordering, zero-fill behavior, and frame-budget processing
semantics while ensuring steady-state render callbacks allocate no arrays.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/audio/noiseSuppressedStream.ts`:
- Around line 63-79: Update the noise-suppressed stream setup around context,
source, addModule, and initializeNoiseSuppressionWorklet to catch setup
failures, close the AudioContext, and stop every sourceStream track before
rethrowing. Add a bounded timeout to initializeNoiseSuppressionWorklet’s
ready/error handshake so a nonresponsive worklet rejects and triggers the same
cleanup path, while preserving normal successful initialization.

In `@src/lib/audio/noiseSuppressionWorklet.ts`:
- Around line 29-43: Update the outputBuffer declaration in
NoiseSuppressionProcessor to use plain Float32Array, removing the
Float32Array<ArrayBufferLike> generic syntax so it remains compatible with
TypeScript 5.2.2.

---

Nitpick comments:
In `@src/lib/audio/noiseSuppressedStream.ts`:
- Around line 112-161: Export a union type for the worklet’s outgoing messages
from noiseSuppressionWorklet.ts, mirroring the existing
NoiseSuppressionWorkletMessage definition and covering ready, error,
processing-error, and long-frame payloads. Import that type into
initializeNoiseSuppressionWorklet and type processor.port.onmessage as
MessageEvent of the shared outgoing-message union instead of treating event.data
as untyped data.

In `@src/lib/audio/noiseSuppressionWorklet.ts`:
- Around line 98-139: Replace the allocation-heavy output buffering used by
process(), processBufferedFrame(), and appendFloat32Arrays with a pre-allocated
Float32Array ring buffer managed by head/tail indices. Enqueue processed frame
samples without creating combined arrays, and dequeue samples directly into
output without outputBuffer.slice(); preserve existing ordering, zero-fill
behavior, and frame-budget processing semantics while ensuring steady-state
render callbacks allocate no arrays.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5fc773c2-6b7e-49d0-8599-440f056b8ace

📥 Commits

Reviewing files that changed from the base of the PR and between d4242a3 and 02b3608.

📒 Files selected for processing (7)
  • electron/ipc/register/settings.ts
  • src/components/launch/LaunchWindow.tsx
  • src/components/launch/popovers/MicPopover.tsx
  • src/hooks/useScreenRecorder.ts
  • src/lib/audio/noiseSuppressedStream.ts
  • src/lib/audio/noiseSuppression.ts
  • src/lib/audio/noiseSuppressionWorklet.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/components/launch/LaunchWindow.tsx
  • electron/ipc/register/settings.ts
  • src/components/launch/popovers/MicPopover.tsx
  • src/lib/audio/noiseSuppression.ts
  • src/hooks/useScreenRecorder.ts

Comment thread src/lib/audio/noiseSuppressedStream.ts Outdated
Comment thread src/lib/audio/noiseSuppressionWorklet.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant