From 770e0490dd6fe658022fc2a23f9333b7d7f14cce Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Thu, 12 Mar 2026 23:42:11 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20increase=20perceived=20audio=20loudness?= =?UTF-8?q?=20=E2=80=94=20+3dB=20master=20boost,=20configurable=20SFX=20vo?= =?UTF-8?q?lume?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three changes to bring output volume closer to native apps: - Insert masterBoost GainNode (+3dB) before the compressor/limiter - Raise default system sound volume from 0.5 to 0.8 - Add configurable Sound Effects Volume slider (0–200%) in Voice settings --- packages/web/src/audio/AudioManager.ts | 16 ++++++--- .../modals/settingsPanels/VoicePanel.tsx | 27 +++++++++++++++ .../src/components/voice/SoundController.tsx | 33 +++++++++++-------- packages/web/src/stores/voiceStore.ts | 11 ++++++- 4 files changed, 68 insertions(+), 19 deletions(-) diff --git a/packages/web/src/audio/AudioManager.ts b/packages/web/src/audio/AudioManager.ts index a07a7b81..dc610068 100644 --- a/packages/web/src/audio/AudioManager.ts +++ b/packages/web/src/audio/AudioManager.ts @@ -12,6 +12,7 @@ export class AudioManager { private silentGain: GainNode | null = null; private analyser: AnalyserNode | null = null; private masterCompressor: DynamicsCompressorNode | null = null; + private masterBoost: GainNode | null = null; private currentInputDeviceId: string = 'default'; private desiredOutputDeviceId: string = 'default'; @@ -61,6 +62,11 @@ export class AudioManager { this.masterCompressor.ratio.value = 4; // gentle limiting, no ducking this.masterCompressor.attack.value = 0.0005; // 0.5ms — catch transient peaks this.masterCompressor.release.value = 0.01; // 10ms — recover quickly + // +3dB boost before the limiter — drives a hotter signal into the + // compressor, raising perceived loudness while peaks are still caught. + this.masterBoost = this.ctx.createGain(); + this.masterBoost.gain.value = 1.41; // +3dB + this.masterBoost.connect(this.masterCompressor); this.masterCompressor.connect(this.ctx.destination); this.inputGain.connect(this.inputDestination); @@ -168,10 +174,10 @@ export class AudioManager { source.loop = options.loop || false; const gainNode = this.ctx.createGain(); - gainNode.gain.value = options.volume ?? 0.5; + gainNode.gain.value = options.volume ?? 0.8; source.connect(gainNode); - gainNode.connect(this.masterCompressor!); + gainNode.connect(this.masterBoost!); source.start(0); return source; @@ -373,14 +379,14 @@ export class AudioManager { } /** - * Returns the master output bus (DynamicsCompressorNode → ctx.destination). + * Returns the master output bus (masterBoost → masterCompressor → ctx.destination). * All audio (remote voice, streams, effects) routes through this node. - * The compressor prevents clipping when multiple sources sum together. + * The +3dB boost raises perceived loudness; the compressor catches peaks. * Output device is controlled via setSinkId on the underlying AudioContext. */ getMasterOutput(): AudioNode { if (!this.ctx) this.initContext(); - return this.masterCompressor!; + return this.masterBoost!; } getContext(): AudioContext | null { diff --git a/packages/web/src/components/modals/settingsPanels/VoicePanel.tsx b/packages/web/src/components/modals/settingsPanels/VoicePanel.tsx index 61f12b9c..17103d40 100644 --- a/packages/web/src/components/modals/settingsPanels/VoicePanel.tsx +++ b/packages/web/src/components/modals/settingsPanels/VoicePanel.tsx @@ -8,9 +8,36 @@ export function VoicePanel() { const setEchoCancellation = useVoiceStore((s) => s.setEchoCancellation); const setAutoGainControl = useVoiceStore((s) => s.setAutoGainControl); const setRnnoiseEnabled = useVoiceStore((s) => s.setRnnoiseEnabled); + const soundEffectVolume = useVoiceStore((s) => s.soundEffectVolume); + const setSoundEffectVolume = useVoiceStore((s) => s.setSoundEffectVolume); return (