Fix bugs and clean noise reduction

This commit is contained in:
2026-03-06 02:22:43 +01:00
parent 0ed9ca93d3
commit 2d84fbd91a
39 changed files with 3443 additions and 1544 deletions

View File

@@ -52,11 +52,14 @@ export class MembersSettingsComponent {
}
changeRole(user: User, role: 'admin' | 'moderator' | 'member'): void {
const roomId = this.server()?.id;
this.store.dispatch(UsersActions.updateUserRole({ userId: user.id,
role }));
this.webrtcService.broadcastMessage({
type: 'role-change',
roomId,
targetUserId: user.id,
role
});

View File

@@ -229,178 +229,4 @@
</div>
</div>
</section>
<!-- Voice Leveling -->
<section>
<div class="flex items-center gap-2 mb-3">
<ng-icon
name="lucideActivity"
class="w-5 h-5 text-muted-foreground"
/>
<h4 class="text-sm font-semibold text-foreground">Voice Leveling</h4>
</div>
<div class="space-y-3">
<!-- Master toggle -->
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-foreground">Voice Leveling</p>
<p class="text-xs text-muted-foreground">Automatically equalise volume across speakers</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
[checked]="voiceLeveling.enabled()"
(change)="onVoiceLevelingToggle()"
id="voice-leveling-toggle"
aria-label="Toggle voice leveling"
class="sr-only peer"
/>
<div
class="w-10 h-5 bg-secondary rounded-full peer peer-checked:bg-primary peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all"
></div>
</label>
</div>
<!-- Advanced controls - visible only when enabled -->
@if (voiceLeveling.enabled()) {
<div class="space-y-3 pl-1 border-l-2 border-primary/20 ml-1">
<!-- Target Loudness -->
<div class="pl-3">
<label
for="target-loudness-slider"
class="block text-xs font-medium text-muted-foreground mb-1"
>
Target Loudness: {{ voiceLeveling.targetDbfs() }} dBFS
</label>
<input
type="range"
[value]="voiceLeveling.targetDbfs()"
(input)="onTargetDbfsChange($event)"
min="-30"
max="-12"
step="1"
id="target-loudness-slider"
class="w-full h-1.5 bg-secondary rounded-lg appearance-none cursor-pointer accent-primary"
/>
<div class="flex justify-between text-[10px] text-muted-foreground/60 mt-0.5">
<span>-30 (quiet)</span>
<span>-12 (loud)</span>
</div>
</div>
<!-- AGC Strength -->
<div class="pl-3">
<label
for="agc-strength-select"
class="block text-xs font-medium text-muted-foreground mb-1"
>AGC Strength</label
>
<select
(change)="onStrengthChange($event)"
id="agc-strength-select"
class="w-full px-3 py-2 bg-secondary rounded-lg border border-border text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-primary"
>
<option
value="low"
[selected]="voiceLeveling.strength() === 'low'"
>
Low (gentle)
</option>
<option
value="medium"
[selected]="voiceLeveling.strength() === 'medium'"
>
Medium
</option>
<option
value="high"
[selected]="voiceLeveling.strength() === 'high'"
>
High (aggressive)
</option>
</select>
</div>
<!-- Max Gain Boost -->
<div class="pl-3">
<label
for="max-gain-slider"
class="block text-xs font-medium text-muted-foreground mb-1"
>
Max Gain Boost: {{ voiceLeveling.maxGainDb() }} dB
</label>
<input
type="range"
[value]="voiceLeveling.maxGainDb()"
(input)="onMaxGainDbChange($event)"
min="3"
max="20"
step="1"
id="max-gain-slider"
class="w-full h-1.5 bg-secondary rounded-lg appearance-none cursor-pointer accent-primary"
/>
<div class="flex justify-between text-[10px] text-muted-foreground/60 mt-0.5">
<span>3 dB (subtle)</span>
<span>20 dB (strong)</span>
</div>
</div>
<!-- Response Speed -->
<div class="pl-3">
<label
for="response-speed-select"
class="block text-xs font-medium text-muted-foreground mb-1"
>
Response Speed
</label>
<select
(change)="onSpeedChange($event)"
id="response-speed-select"
class="w-full px-3 py-2 bg-secondary rounded-lg border border-border text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-primary"
>
<option
value="slow"
[selected]="voiceLeveling.speed() === 'slow'"
>
Slow (natural)
</option>
<option
value="medium"
[selected]="voiceLeveling.speed() === 'medium'"
>
Medium
</option>
<option
value="fast"
[selected]="voiceLeveling.speed() === 'fast'"
>
Fast (aggressive)
</option>
</select>
</div>
<!-- Noise Floor Gate -->
<div class="pl-3 flex items-center justify-between">
<div>
<p class="text-sm font-medium text-foreground">Noise Floor Gate</p>
<p class="text-xs text-muted-foreground">Prevents boosting silence</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
[checked]="voiceLeveling.noiseGate()"
(change)="onNoiseGateToggle()"
id="noise-gate-toggle"
aria-label="Toggle noise floor gate"
class="sr-only peer"
/>
<div
class="w-10 h-5 bg-secondary rounded-full peer peer-checked:bg-primary peer-checked:after:translate-x-full after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all"
></div>
</label>
</div>
</div>
}
</div>
</section>
</div>

View File

@@ -10,12 +10,11 @@ import { NgIcon, provideIcons } from '@ng-icons/core';
import {
lucideMic,
lucideHeadphones,
lucideAudioLines,
lucideActivity
lucideAudioLines
} from '@ng-icons/lucide';
import { WebRTCService } from '../../../../core/services/webrtc.service';
import { VoiceLevelingService } from '../../../../core/services/voice-leveling.service';
import { VoicePlaybackService } from '../../../voice/voice-controls/services/voice-playback.service';
import { NotificationAudioService, AppSound } from '../../../../core/services/notification-audio.service';
import { STORAGE_KEY_VOICE_SETTINGS } from '../../../../core/constants';
@@ -36,15 +35,14 @@ interface AudioDevice {
provideIcons({
lucideMic,
lucideHeadphones,
lucideAudioLines,
lucideActivity
lucideAudioLines
})
],
templateUrl: './voice-settings.component.html'
})
export class VoiceSettingsComponent {
private webrtcService = inject(WebRTCService);
readonly voiceLeveling = inject(VoiceLevelingService);
private voicePlayback = inject(VoicePlaybackService);
readonly audioService = inject(NotificationAudioService);
inputDevices = signal<AudioDevice[]>([]);
@@ -56,7 +54,7 @@ export class VoiceSettingsComponent {
audioBitrate = signal(96);
latencyProfile = signal<'low' | 'balanced' | 'high'>('balanced');
includeSystemAudio = signal(false);
noiseReduction = signal(false);
noiseReduction = signal(true);
constructor() {
this.loadVoiceSettings();
@@ -123,6 +121,11 @@ export class VoiceSettingsComponent {
if (this.noiseReduction() !== this.webrtcService.isNoiseReductionEnabled()) {
this.webrtcService.toggleNoiseReduction(this.noiseReduction());
}
// Apply persisted volume levels to the live audio pipelines
this.webrtcService.setInputVolume(this.inputVolume() / 100);
this.webrtcService.setOutputVolume(this.outputVolume() / 100);
this.voicePlayback.updateOutputVolume(this.outputVolume() / 100);
}
saveVoiceSettings(): void {
@@ -162,6 +165,7 @@ export class VoiceSettingsComponent {
const input = event.target as HTMLInputElement;
this.inputVolume.set(parseInt(input.value, 10));
this.webrtcService.setInputVolume(this.inputVolume() / 100);
this.saveVoiceSettings();
}
@@ -170,6 +174,7 @@ export class VoiceSettingsComponent {
this.outputVolume.set(parseInt(input.value, 10));
this.webrtcService.setOutputVolume(this.outputVolume() / 100);
this.voicePlayback.updateOutputVolume(this.outputVolume() / 100);
this.saveVoiceSettings();
}
@@ -203,40 +208,6 @@ export class VoiceSettingsComponent {
this.saveVoiceSettings();
}
/* ── Voice Leveling handlers ───────────────────────────────── */
onVoiceLevelingToggle(): void {
this.voiceLeveling.setEnabled(!this.voiceLeveling.enabled());
}
onTargetDbfsChange(event: Event): void {
const input = event.target as HTMLInputElement;
this.voiceLeveling.setTargetDbfs(parseInt(input.value, 10));
}
onStrengthChange(event: Event): void {
const select = event.target as HTMLSelectElement;
this.voiceLeveling.setStrength(select.value as 'low' | 'medium' | 'high');
}
onMaxGainDbChange(event: Event): void {
const input = event.target as HTMLInputElement;
this.voiceLeveling.setMaxGainDb(parseInt(input.value, 10));
}
onSpeedChange(event: Event): void {
const select = event.target as HTMLSelectElement;
this.voiceLeveling.setSpeed(select.value as 'slow' | 'medium' | 'fast');
}
onNoiseGateToggle(): void {
this.voiceLeveling.setNoiseGate(!this.voiceLeveling.noiseGate());
}
onNotificationVolumeChange(event: Event): void {
const input = event.target as HTMLInputElement;

View File

@@ -68,7 +68,7 @@ export class SettingsComponent implements OnInit {
newServerUrl = '';
autoReconnect = true;
searchAllServers = true;
noiseReduction = false;
noiseReduction = true;
/** Load persisted connection settings on component init. */
ngOnInit(): void {