Files
cinny/src/app/utils/lotusDenoiseUtils.ts
T
Lotus CIandClaude Opus 5.5 1ebc42863e
CI / Build & Quality Checks (push) Skipped
CI / Secret scan (gitleaks) (push) Skipped
CI / Playwright smoke (e2e) (push) Skipped
CI / Build & Quality Checks (pull_request) Successful in 1m40s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 6s
CI / Playwright smoke (e2e) (pull_request) Successful in 19m45s
fix(settings): measured CPU and download figures for the noise-suppression models
The CPU column was guessed (DeepFilterNet "25-50%", DTLN "10-20%") with no
unit, so it read like Task Manager's whole-CPU figure and looked wildly off.
Measured with each model running exactly as Lotus Call runs it (real-time
speech, browser process CPU time from /proc minus a no-model baseline,
Ryzen 7 PRO 4750GE): DeepFilterNet ~30%, DTLN ~9%, RNNoise ~3%, Speex ~2%
of one core. Label it "one core" and explain the Task Manager relation.
DTLN's download was listed as ~4 MB but fetches ~20 MB (17 MB worklet with
the runtime inlined, plus 4 MB of models); DFN's ~18 MB is right (17.6 MB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-29 21:35:20 -04:00

102 lines
3.6 KiB
TypeScript

/**
* Detection utilities + model catalog for Lotus ML noise suppression
* (DeepFilterNet 3 / DTLN / RNNoise / Speex). The catalog is ordered by
* quality (and, correspondingly, CPU cost) — highest first — and drives the
* order of the model dropdown in settings.
*/
import { DenoiseModelId } from '../state/settings';
// CPU = share of ONE core while in a call, measured with each model running
// exactly as Lotus Call runs it (Ryzen 7 PRO 4750GE, Chromium); download = bytes
// actually fetched on first use. Re-measure when a model or its settings change.
export type DenoiseModel = {
id: DenoiseModelId;
name: string;
description: string;
cpuUsage: string;
binarySize: string;
transients: 'Poor' | 'Good' | 'Excellent';
voiceQuality: 'Moderate' | 'High' | 'Very High';
};
// Ordered best-quality (highest CPU) first — this is the dropdown order.
export const DENOISE_MODELS: DenoiseModel[] = [
{
id: 'deepfilternet',
name: 'DeepFilterNet 3 (beta)',
description:
'Studio-grade deep-learning model (48 kHz fullband, ONNX). Best quality; highest CPU and a larger one-time download.',
cpuUsage: '~30%',
binarySize: '~18 MB',
transients: 'Excellent',
voiceQuality: 'Very High',
},
{
id: 'dtln',
name: 'DTLN (beta)',
description:
'Dual-signal deep-learning model (16 kHz). Strong on transient noise; moderate CPU.',
cpuUsage: '~9%',
binarySize: '~20 MB',
transients: 'Excellent',
voiceQuality: 'High',
},
{
id: 'rnnoise',
name: 'RNNoise',
description:
'Lightweight hybrid model (48 kHz). Very low CPU; good for steady noise like fans, but can sound processed at full strength.',
cpuUsage: '~3%',
binarySize: '< 1 MB',
transients: 'Good',
voiceQuality: 'Moderate',
},
{
id: 'speex',
name: 'Speex (Legacy)',
description:
'Classic DSP noise suppressor. Minimal CPU, gentlest on voice; weakest suppression.',
cpuUsage: '~2%',
binarySize: '< 1 MB',
transients: 'Poor',
voiceQuality: 'Moderate',
},
];
export const isMLDenoiseSupported = (): boolean => {
if (typeof window === 'undefined') return false;
// Requirements:
// 1. AudioContext/webkitAudioContext (Web Audio API)
// 2. AudioWorklet (Real-time processing in a background thread)
// 3. getUserMedia (Microphone access)
const hasAudioContext = !!(
window.AudioContext || (window as Window & { webkitAudioContext?: unknown }).webkitAudioContext
);
// Use `typeof` rather than `!!AudioWorkletNode`: on a browser that has
// AudioContext but no AudioWorkletNode binding (older Safari/Edge), a bare
// reference throws ReferenceError, making this feature-detection helper throw
// instead of returning false.
const hasAudioWorklet = hasAudioContext && typeof AudioWorkletNode !== 'undefined';
const hasGetUserMedia = !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
// Every ML model compiles WebAssembly (and DFN/DTLN load worklets via blob
// URLs). Under a strict CSP without `wasm-unsafe-eval` (e.g. some desktop/Tauri
// shells) WASM is unavailable, so gate on it — otherwise we'd offer ML and then
// silently fall back to the raw mic in-call.
const hasWasm =
typeof WebAssembly !== 'undefined' && typeof WebAssembly.instantiate === 'function';
return hasAudioWorklet && hasGetUserMedia && hasWasm;
};
/**
* EXACT requirements for ML Denoise (for UI display).
*/
export const ML_DENOISE_REQUIREMENTS = [
'Modern browser with Web Audio API support',
'AudioWorklet support (Chrome 66+, Firefox 76+, Safari 14.1+)',
'WebAssembly (WASM) support',
'Microphone access',
];