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>
190 lines
5.6 KiB
TypeScript
190 lines
5.6 KiB
TypeScript
/*
|
||
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 (0–1)
|
||
export const vadPositiveThreshold = new Setting<number>("vad-positive-threshold", 0.5);
|
||
// Probability below which the VAD closes the gate (0–1)
|
||
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,
|
||
);
|