Files
element-call-custom/src/settings/settings.ts
mk 859db651e0 feat: add VAD threshold controls and smooth gate ramp
Replace the hard 0/1 VAD gate with a 20ms ramp in the worklet to prevent
clicks on open/close transitions. Expose positive and negative speech
probability thresholds as user-adjustable settings (defaults 0.5/0.35).
Sliders with restore-defaults button added to the VAD section of the
audio settings tab.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:57:35 -03:00

190 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { logger } from "matrix-js-sdk/lib/logger";
import { BehaviorSubject } from "rxjs";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { type Behavior } from "../state/Behavior";
import { useBehavior } from "../useBehavior";
export class Setting<T> {
public constructor(
key: string,
public readonly defaultValue: T,
) {
this.key = `matrix-setting-${key}`;
const storedValue = localStorage.getItem(this.key);
let initialValue = defaultValue;
if (storedValue !== null) {
try {
initialValue = JSON.parse(storedValue);
} catch (e) {
logger.warn(
`Invalid value stored for setting ${key}: ${storedValue}.`,
e,
);
}
}
this._value$ = new BehaviorSubject(initialValue);
this.value$ = this._value$;
this._lastUpdateReason$ = new BehaviorSubject<string | null>(null);
this.lastUpdateReason$ = this._lastUpdateReason$;
}
private readonly key: string;
private readonly _value$: BehaviorSubject<T>;
private readonly _lastUpdateReason$: BehaviorSubject<string | null>;
public readonly value$: Behavior<T>;
public readonly lastUpdateReason$: Behavior<string | null>;
public readonly setValue = (value: T, reason?: string): void => {
this._value$.next(value);
this._lastUpdateReason$.next(reason ?? null);
localStorage.setItem(this.key, JSON.stringify(value));
};
public readonly getValue = (): T => {
return this._value$.getValue();
};
}
/**
* React hook that returns a settings's current value and a setter.
*/
export function useSetting<T>(setting: Setting<T>): [T, (value: T) => void] {
return [useBehavior(setting.value$), setting.setValue];
}
// null = undecided
export const optInAnalytics = new Setting<boolean | null>(
"opt-in-analytics",
null,
);
// TODO: This setting can be disabled. Work out an approach to disableable
// settings thats works for Observables in addition to React.
export const useOptInAnalytics = (): [
boolean | null,
((value: boolean | null) => void) | null,
] => {
const setting = useSetting(optInAnalytics);
return PosthogAnalytics.instance.isEnabled() ? setting : [false, null];
};
export const developerMode = new Setting("developer-settings-tab", false);
export const duplicateTiles = new Setting("duplicate-tiles", 0);
export const debugTileLayout = new Setting("debug-tile-layout", false);
export const showConnectionStats = new Setting<boolean>(
"show-connection-stats",
false,
);
export const audioInput = new Setting<string | undefined>(
"audio-input",
undefined,
);
export const audioOutput = new Setting<string | undefined>(
"audio-output",
undefined,
);
export const videoInput = new Setting<string | undefined>(
"video-input",
undefined,
);
export const backgroundBlur = new Setting<boolean>("background-blur", false);
export const showHandRaisedTimer = new Setting<boolean>(
"hand-raised-show-timer",
false,
);
export const showReactions = new Setting<boolean>("reactions-show", true);
export const playReactionsSound = new Setting<boolean>(
"reactions-play-sound",
true,
);
export const soundEffectVolume = new Setting<number>(
"sound-effect-volume",
0.5,
);
export const muteAllAudio = new Setting<boolean>("mute-all-audio", false);
export const alwaysShowSelf = new Setting<boolean>("always-show-self", true);
export const alwaysShowIphoneEarpiece = new Setting<boolean>(
"always-show-iphone-earpiece",
false,
);
export const noiseGateEnabled = new Setting<boolean>(
"noise-gate-enabled",
false,
);
// Threshold in dBFS — gate opens above this, closes below it
export const noiseGateThreshold = new Setting<number>(
"noise-gate-threshold",
-60,
);
// Time in ms for the gate to fully open after signal exceeds threshold
export const noiseGateAttack = new Setting<number>("noise-gate-attack", 25);
// Time in ms the gate stays open after signal drops below threshold
export const noiseGateHold = new Setting<number>("noise-gate-hold", 200);
// Time in ms for the gate to fully close after hold expires
export const noiseGateRelease = new Setting<number>("noise-gate-release", 150);
export const vadEnabled = new Setting<boolean>("vad-enabled", false);
// Probability above which the VAD opens the gate (01)
export const vadPositiveThreshold = new Setting<number>("vad-positive-threshold", 0.5);
// Probability below which the VAD closes the gate (01)
export const vadNegativeThreshold = new Setting<number>("vad-negative-threshold", 0.35);
export const transientSuppressorEnabled = new Setting<boolean>(
"transient-suppressor-enabled",
false,
);
// How many dB above the background RMS a peak must be to trigger suppression
export const transientThreshold = new Setting<number>(
"transient-suppressor-threshold",
15,
);
// Time in ms for suppression to fade after transient ends
export const transientRelease = new Setting<number>(
"transient-suppressor-release",
80,
);
export enum MatrixRTCMode {
Legacy = "legacy",
Compatibility = "compatibility",
/** This implies using
* - sticky events
* - hashed RTC backend identity
* - the new endpoint for the jwt token on the local membership (remote memberships will always try the new jwt endpoint first -> then the legacy one)
* - use the hashed identity for the local membership
*/
Matrix_2_0 = "matrix_2_0",
}
export const matrixRTCMode = new Setting<MatrixRTCMode>(
"matrix-rtc-mode",
MatrixRTCMode.Legacy,
);
export const customLivekitUrl = new Setting<string | null>(
"custom-livekit-url",
null,
);