feat(voice): add noise suppression backends and rollout (#2706)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-11 22:24:05 +02:00
1 parent b30a4f5d14
commit 172791316b
97 files changed
+10877 -3157

No files matched your search

@@ -18,6 +18,14 @@ import {
GatewayRolloutConfigResponse,
GatewayRolloutConfigUpdateRequest,
} from '@fluxer/schema/src/domains/admin/GatewayRolloutSchemas';
import {
VoiceNoiseSuppressionConfigResponse,
VoiceNoiseSuppressionConfigUpdateRequest,
} from '@fluxer/schema/src/domains/admin/VoiceNoiseSuppressionSchemas';
import {
ExperimentDeliveryConfigResponse,
ExperimentDeliveryConfigUpdateRequest,
} from '@fluxer/schema/src/domains/experiment/ExperimentSchemas';
import {GuildMemberResponse} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
import {MessageResponseSchema} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
import {ChannelTypeSchema} from '@fluxer/schema/src/primitives/ChannelValidators';
@@ -629,6 +637,8 @@ const InstanceIntegrationsResponse = z.object({
export const InstanceConfigResponse = z.object({
sso: SsoConfigResponse,
gateway_rollout: GatewayRolloutConfigResponse,
voice_noise_suppression: VoiceNoiseSuppressionConfigResponse,
experiment_delivery: ExperimentDeliveryConfigResponse,
registration: InstanceRegistrationResponse,
self_hosted: z.boolean(),
app_public: AppPublicConfigResponse,
@@ -641,6 +651,8 @@ export type InstanceConfigResponse = z.infer<typeof InstanceConfigResponse>;
export const InstanceConfigUpdateRequest = z.object({
gateway_rollout: GatewayRolloutConfigUpdateRequest.nullish(),
voice_noise_suppression: VoiceNoiseSuppressionConfigUpdateRequest.nullish(),
experiment_delivery: ExperimentDeliveryConfigUpdateRequest.nullish(),
registration: z
.object({
mode: RegistrationModeSchema.optional(),
@@ -0,0 +1,377 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {
DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG,
INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT,
resolveVoiceNoiseSuppressionAssignment,
resolveVoiceNoiseSuppressionForCall,
VOICE_NOISE_SUPPRESSION_BACKENDS,
type VoiceNoiseSuppressionAssignmentResponse,
type VoiceNoiseSuppressionBackend,
type VoiceNoiseSuppressionConfig,
} from '@fluxer/schema/src/domains/admin/VoiceNoiseSuppressionSchemas';
import {experimentBucket} from '@fluxer/schema/src/domains/experiment/ExperimentBucket';
import {describe, expect, test} from 'vitest';
const TARGETED_USER_ID = '1000000000000000001';
const OTHER_USER_ID = '1000000000000000002';
const GUILD_ID = '2000000000000000001';
const OTHER_GUILD_ID = '2000000000000000002';
function createConfig(overrides: Partial<VoiceNoiseSuppressionConfig> = {}): VoiceNoiseSuppressionConfig {
return {
...DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG,
enabled_backends: [...DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG.enabled_backends],
included_user_ids: [],
excluded_user_ids: [],
guild_overrides: [],
...overrides,
};
}
function createAssignment(
overrides: Partial<VoiceNoiseSuppressionAssignmentResponse> = {},
): VoiceNoiseSuppressionAssignmentResponse {
return {
enabled: true,
config_version: 7,
user_targeted: true,
backend: 'rnnoise',
source: 'canary',
guild_overrides: [],
enabled_backends: [...VOICE_NOISE_SUPPRESSION_BACKENDS],
allow_user_override: false,
stereo_enabled: false,
suppression_strength: 80,
...overrides,
};
}
function syntheticUserIds(count: number): Array<string> {
const ids: Array<string> = [];
for (let index = 0; index < count; index++) {
ids.push((1400000000000000000n + BigInt(index)).toString());
}
return ids;
}
function targetedUserIds(config: VoiceNoiseSuppressionConfig, userIds: ReadonlyArray<string>): Set<string> {
const targeted = new Set<string>();
for (const userId of userIds) {
if (resolveVoiceNoiseSuppressionAssignment(config, userId).user_targeted) {
targeted.add(userId);
}
}
return targeted;
}
describe('experimentBucket', () => {
test('stays inside the basis point range for every synthetic id', () => {
for (const userId of syntheticUserIds(500)) {
const bucket = experimentBucket(userId, 'voice-ns-v1');
expect(Number.isInteger(bucket)).toBe(true);
expect(bucket).toBeGreaterThanOrEqual(0);
expect(bucket).toBeLessThan(10000);
}
});
test('is stable for the same user id and salt', () => {
expect(experimentBucket(TARGETED_USER_ID, 'voice-ns-v1')).toBe(experimentBucket(TARGETED_USER_ID, 'voice-ns-v1'));
});
test('changes with the salt for at least most user ids', () => {
const userIds = syntheticUserIds(200);
const changed = userIds.filter(
(userId) => experimentBucket(userId, 'voice-ns-v1') !== experimentBucket(userId, 'voice-ns-v2'),
);
expect(changed.length).toBeGreaterThan(userIds.length - 5);
});
});
describe('resolveVoiceNoiseSuppressionAssignment', () => {
test('returns the inert assignment when the master switch is off', () => {
const config = createConfig({
enabled: false,
config_version: 4,
rollout_basis_points: 10000,
included_user_ids: [TARGETED_USER_ID],
guild_overrides: [{guild_id: GUILD_ID, backend: 'rnnoise'}],
});
expect(resolveVoiceNoiseSuppressionAssignment(config, TARGETED_USER_ID)).toEqual({
...INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT,
config_version: 4,
});
});
test('returns the inert assignment for the default config', () => {
expect(resolveVoiceNoiseSuppressionAssignment(createConfig(), TARGETED_USER_ID)).toEqual(
INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT,
);
});
test('denylist beats allowlist', () => {
const config = createConfig({
enabled: true,
included_user_ids: [TARGETED_USER_ID],
excluded_user_ids: [TARGETED_USER_ID],
});
const assignment = resolveVoiceNoiseSuppressionAssignment(config, TARGETED_USER_ID);
expect(assignment.user_targeted).toBe(false);
expect(assignment.backend).toBeNull();
expect(assignment.source).toBeNull();
});
test('denylist beats canary', () => {
const config = createConfig({
enabled: true,
rollout_basis_points: 10000,
excluded_user_ids: [TARGETED_USER_ID],
});
const assignment = resolveVoiceNoiseSuppressionAssignment(config, TARGETED_USER_ID);
expect(assignment.user_targeted).toBe(false);
expect(assignment.source).toBeNull();
expect(resolveVoiceNoiseSuppressionAssignment(config, OTHER_USER_ID).user_targeted).toBe(true);
});
test('denylist strips guild overrides and the client-side knobs', () => {
const config = createConfig({
enabled: true,
allow_user_override: true,
stereo_enabled: true,
rollout_basis_points: 10000,
excluded_user_ids: [TARGETED_USER_ID],
guild_overrides: [{guild_id: GUILD_ID, backend: 'rnnoise'}],
});
const assignment = resolveVoiceNoiseSuppressionAssignment(config, TARGETED_USER_ID);
expect(assignment.guild_overrides).toEqual([]);
expect(assignment.enabled_backends).toEqual([]);
expect(assignment.allow_user_override).toBe(false);
expect(assignment.stereo_enabled).toBe(false);
});
test('allowlist gives source user_rule outside the canary', () => {
const config = createConfig({
enabled: true,
default_backend: 'gtcrn',
rollout_basis_points: 0,
included_user_ids: [TARGETED_USER_ID],
});
const assignment = resolveVoiceNoiseSuppressionAssignment(config, TARGETED_USER_ID);
expect(assignment.user_targeted).toBe(true);
expect(assignment.backend).toBe('gtcrn');
expect(assignment.source).toBe('user_rule');
});
test.each([
{basisPoints: 0, targeted: false},
{basisPoints: 10000, targeted: true},
])('canary at $basisPoints basis points targets $targeted', ({basisPoints, targeted}) => {
const config = createConfig({enabled: true, rollout_basis_points: basisPoints});
for (const userId of syntheticUserIds(200)) {
const assignment = resolveVoiceNoiseSuppressionAssignment(config, userId);
expect(assignment.user_targeted).toBe(targeted);
expect(assignment.source).toBe(targeted ? 'canary' : null);
}
});
test('raising the rollout basis points only ever adds users', () => {
const userIds = syntheticUserIds(2000);
const atOneThousand = targetedUserIds(createConfig({enabled: true, rollout_basis_points: 1000}), userIds);
const atTwoThousand = targetedUserIds(createConfig({enabled: true, rollout_basis_points: 2000}), userIds);
expect(atOneThousand.size).toBeGreaterThan(0);
for (const userId of atOneThousand) {
expect(atTwoThousand.has(userId)).toBe(true);
}
expect(atTwoThousand.size).toBeGreaterThan(atOneThousand.size);
});
test('the canary set follows the salt', () => {
const userIds = syntheticUserIds(2000);
const first = targetedUserIds(
createConfig({enabled: true, rollout_basis_points: 5000, rollout_salt: 'voice-ns-v1'}),
userIds,
);
const second = targetedUserIds(
createConfig({enabled: true, rollout_basis_points: 5000, rollout_salt: 'voice-ns-v2'}),
userIds,
);
expect(first).not.toEqual(second);
});
test('a default backend missing from enabled_backends yields no backend and no source', () => {
const config = createConfig({
enabled: true,
default_backend: 'deep_filter',
enabled_backends: ['none', 'standard'],
rollout_basis_points: 10000,
included_user_ids: [OTHER_USER_ID],
});
for (const userId of [TARGETED_USER_ID, OTHER_USER_ID]) {
const assignment = resolveVoiceNoiseSuppressionAssignment(config, userId);
expect(assignment.user_targeted).toBe(false);
expect(assignment.backend).toBeNull();
expect(assignment.source).toBeNull();
}
});
test('guild overrides naming a disabled backend are filtered out', () => {
const config = createConfig({
enabled: true,
default_backend: 'standard',
enabled_backends: ['none', 'standard', 'rnnoise'],
rollout_basis_points: 10000,
guild_overrides: [
{guild_id: GUILD_ID, backend: 'rnnoise'},
{guild_id: OTHER_GUILD_ID, backend: 'deep_filter'},
],
});
expect(resolveVoiceNoiseSuppressionAssignment(config, TARGETED_USER_ID).guild_overrides).toEqual([
{guild_id: GUILD_ID, backend: 'rnnoise'},
]);
});
test('guild overrides survive the allowlist path too', () => {
const config = createConfig({
enabled: true,
enabled_backends: ['none', 'standard', 'rnnoise'],
included_user_ids: [TARGETED_USER_ID],
guild_overrides: [
{guild_id: GUILD_ID, backend: 'rnnoise'},
{guild_id: OTHER_GUILD_ID, backend: 'gtcrn'},
],
});
expect(resolveVoiceNoiseSuppressionAssignment(config, TARGETED_USER_ID).guild_overrides).toEqual([
{guild_id: GUILD_ID, backend: 'rnnoise'},
]);
});
test('echoes the config version on every path', () => {
const config = createConfig({enabled: true, config_version: 11, rollout_basis_points: 10000});
expect(resolveVoiceNoiseSuppressionAssignment(config, TARGETED_USER_ID).config_version).toBe(11);
});
});
describe('resolveVoiceNoiseSuppressionForCall', () => {
test('returns null when the master switch is off', () => {
const assignment = createAssignment({
enabled: false,
user_targeted: true,
backend: 'rnnoise',
source: 'user_rule',
allow_user_override: true,
guild_overrides: [{guild_id: GUILD_ID, backend: 'gtcrn'}],
});
expect(resolveVoiceNoiseSuppressionForCall(assignment, GUILD_ID, 'speex')).toBeNull();
expect(resolveVoiceNoiseSuppressionForCall(assignment, null, null)).toBeNull();
});
test('returns null when nothing targets the user', () => {
const assignment = createAssignment({user_targeted: false, backend: null, source: null});
expect(resolveVoiceNoiseSuppressionForCall(assignment, GUILD_ID, null)).toBeNull();
expect(resolveVoiceNoiseSuppressionForCall(assignment, null, null)).toBeNull();
});
test('user_rule beats guild_rule', () => {
const assignment = createAssignment({
backend: 'rnnoise',
source: 'user_rule',
guild_overrides: [{guild_id: GUILD_ID, backend: 'gtcrn'}],
});
expect(resolveVoiceNoiseSuppressionForCall(assignment, GUILD_ID, null)).toMatchObject({
backend: 'rnnoise',
source: 'user_rule',
});
});
test('guild_rule beats canary', () => {
const assignment = createAssignment({
backend: 'rnnoise',
source: 'canary',
guild_overrides: [{guild_id: GUILD_ID, backend: 'gtcrn'}],
});
expect(resolveVoiceNoiseSuppressionForCall(assignment, GUILD_ID, null)).toMatchObject({
backend: 'gtcrn',
source: 'guild_rule',
});
});
test('a guild override targets an otherwise untargeted user', () => {
const assignment = createAssignment({
user_targeted: false,
backend: null,
source: null,
guild_overrides: [{guild_id: GUILD_ID, backend: 'speex'}],
});
expect(resolveVoiceNoiseSuppressionForCall(assignment, GUILD_ID, null)).toMatchObject({
backend: 'speex',
source: 'guild_rule',
});
expect(resolveVoiceNoiseSuppressionForCall(assignment, OTHER_GUILD_ID, null)).toBeNull();
});
test('a DM falls through to the user level result', () => {
const assignment = createAssignment({
backend: 'rnnoise',
source: 'canary',
guild_overrides: [{guild_id: GUILD_ID, backend: 'gtcrn'}],
});
expect(resolveVoiceNoiseSuppressionForCall(assignment, null, null)).toMatchObject({
backend: 'rnnoise',
source: 'canary',
});
});
test.each([
{allowUserOverride: true, preference: 'speex', expectedBackend: 'speex', expectedSource: 'user_override'},
{allowUserOverride: true, preference: 'gtcrn', expectedBackend: 'rnnoise', expectedSource: 'canary'},
{allowUserOverride: false, preference: 'speex', expectedBackend: 'rnnoise', expectedSource: 'canary'},
{allowUserOverride: true, preference: null, expectedBackend: 'rnnoise', expectedSource: 'canary'},
])('allow_user_override $allowUserOverride with preference $preference resolves to $expectedSource', ({
allowUserOverride,
preference,
expectedBackend,
expectedSource,
}) => {
const assignment = createAssignment({
backend: 'rnnoise',
source: 'canary',
allow_user_override: allowUserOverride,
enabled_backends: ['none', 'standard', 'speex', 'rnnoise'],
});
expect(
resolveVoiceNoiseSuppressionForCall(assignment, null, preference as VoiceNoiseSuppressionBackend | null),
).toMatchObject({backend: expectedBackend, source: expectedSource});
});
test('a user override also outranks a guild rule', () => {
const assignment = createAssignment({
user_targeted: false,
backend: null,
source: null,
allow_user_override: true,
enabled_backends: ['none', 'standard', 'speex'],
guild_overrides: [{guild_id: GUILD_ID, backend: 'standard'}],
});
expect(resolveVoiceNoiseSuppressionForCall(assignment, GUILD_ID, 'speex')).toMatchObject({
backend: 'speex',
source: 'user_override',
});
});
test('carries the client knobs through', () => {
const assignment = createAssignment({
backend: 'rnnoise',
source: 'user_rule',
stereo_enabled: true,
suppression_strength: 42,
config_version: 19,
});
expect(resolveVoiceNoiseSuppressionForCall(assignment, null, null)).toEqual({
backend: 'rnnoise',
source: 'user_rule',
stereoEnabled: true,
suppressionStrength: 42,
configVersion: 19,
});
});
});
@@ -0,0 +1,245 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {z} from 'zod';
import {EXPERIMENT_BUCKET_RESOLUTION, experimentBucket} from '../experiment/ExperimentBucket';
export const VOICE_NOISE_SUPPRESSION_BACKENDS = [
'none',
'standard',
'gate',
'speex',
'rnnoise',
'gtcrn',
'deep_filter',
] as const;
export type VoiceNoiseSuppressionBackend = (typeof VOICE_NOISE_SUPPRESSION_BACKENDS)[number];
const VoiceNoiseSuppressionBackendSchema = z.enum(VOICE_NOISE_SUPPRESSION_BACKENDS);
const VOICE_NOISE_SUPPRESSION_ROLLOUT_BASIS_POINTS_MAX = EXPERIMENT_BUCKET_RESOLUTION;
const VOICE_NOISE_SUPPRESSION_MAX_TARGETED_USERS = 1000;
const VOICE_NOISE_SUPPRESSION_MAX_GUILD_OVERRIDES = 200;
const DEFAULT_VOICE_NOISE_SUPPRESSION_SALT = 'voice-ns-v1';
const DEFAULT_VOICE_NOISE_SUPPRESSION_ENABLED_BACKENDS: ReadonlyArray<VoiceNoiseSuppressionBackend> = [
'none',
'standard',
'gate',
'speex',
'rnnoise',
'gtcrn',
'deep_filter',
];
const SnowflakeListSchema = (max: number) =>
z
.array(z.string().regex(/^\d{1,20}$/u))
.max(max)
.default([]);
const VoiceNoiseSuppressionGuildOverrideSchema = z.object({
guild_id: z.string().regex(/^\d{1,20}$/u),
backend: VoiceNoiseSuppressionBackendSchema,
});
export const VoiceNoiseSuppressionConfigSchema = z.object({
enabled: z.boolean().default(false),
config_version: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER).default(0),
default_backend: VoiceNoiseSuppressionBackendSchema.default('standard'),
enabled_backends: z
.array(VoiceNoiseSuppressionBackendSchema)
.max(VOICE_NOISE_SUPPRESSION_BACKENDS.length)
.default([...DEFAULT_VOICE_NOISE_SUPPRESSION_ENABLED_BACKENDS]),
allow_user_override: z.boolean().default(true),
rollout_basis_points: z.number().int().min(0).max(VOICE_NOISE_SUPPRESSION_ROLLOUT_BASIS_POINTS_MAX).default(0),
rollout_salt: z.string().trim().min(1).max(64).default(DEFAULT_VOICE_NOISE_SUPPRESSION_SALT),
included_user_ids: SnowflakeListSchema(VOICE_NOISE_SUPPRESSION_MAX_TARGETED_USERS),
excluded_user_ids: SnowflakeListSchema(VOICE_NOISE_SUPPRESSION_MAX_TARGETED_USERS),
guild_overrides: z
.array(VoiceNoiseSuppressionGuildOverrideSchema)
.max(VOICE_NOISE_SUPPRESSION_MAX_GUILD_OVERRIDES)
.default([]),
stereo_enabled: z.boolean().default(false),
suppression_strength: z.number().int().min(0).max(100).default(80),
});
export type VoiceNoiseSuppressionConfig = z.infer<typeof VoiceNoiseSuppressionConfigSchema>;
export const DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG: VoiceNoiseSuppressionConfig = {
enabled: false,
config_version: 0,
default_backend: 'standard',
enabled_backends: [...DEFAULT_VOICE_NOISE_SUPPRESSION_ENABLED_BACKENDS],
allow_user_override: true,
rollout_basis_points: 0,
rollout_salt: DEFAULT_VOICE_NOISE_SUPPRESSION_SALT,
included_user_ids: [],
excluded_user_ids: [],
guild_overrides: [],
stereo_enabled: false,
suppression_strength: 80,
};
export const VoiceNoiseSuppressionConfigUpdateRequest = z.object({
enabled: z.boolean().optional(),
default_backend: VoiceNoiseSuppressionBackendSchema.optional(),
enabled_backends: z.array(VoiceNoiseSuppressionBackendSchema).max(VOICE_NOISE_SUPPRESSION_BACKENDS.length).optional(),
allow_user_override: z.boolean().optional(),
rollout_basis_points: z.number().int().min(0).max(VOICE_NOISE_SUPPRESSION_ROLLOUT_BASIS_POINTS_MAX).optional(),
rollout_salt: z.string().trim().min(1).max(64).optional(),
included_user_ids: z
.array(z.string().regex(/^\d{1,20}$/u))
.max(VOICE_NOISE_SUPPRESSION_MAX_TARGETED_USERS)
.optional(),
excluded_user_ids: z
.array(z.string().regex(/^\d{1,20}$/u))
.max(VOICE_NOISE_SUPPRESSION_MAX_TARGETED_USERS)
.optional(),
guild_overrides: z
.array(VoiceNoiseSuppressionGuildOverrideSchema)
.max(VOICE_NOISE_SUPPRESSION_MAX_GUILD_OVERRIDES)
.optional(),
stereo_enabled: z.boolean().optional(),
suppression_strength: z.number().int().min(0).max(100).optional(),
});
export type VoiceNoiseSuppressionConfigUpdateRequest = z.infer<typeof VoiceNoiseSuppressionConfigUpdateRequest>;
export const VoiceNoiseSuppressionConfigResponse = VoiceNoiseSuppressionConfigSchema;
export type VoiceNoiseSuppressionConfigResponse = z.infer<typeof VoiceNoiseSuppressionConfigResponse>;
const VOICE_NOISE_SUPPRESSION_ASSIGNMENT_SOURCES = ['user_rule', 'canary'] as const;
const VoiceNoiseSuppressionAssignmentSourceSchema = z.enum(VOICE_NOISE_SUPPRESSION_ASSIGNMENT_SOURCES);
export const VoiceNoiseSuppressionAssignmentResponse = z.object({
enabled: z.boolean(),
config_version: z.number().int(),
user_targeted: z.boolean(),
backend: VoiceNoiseSuppressionBackendSchema.nullable(),
source: VoiceNoiseSuppressionAssignmentSourceSchema.nullable(),
guild_overrides: z.array(VoiceNoiseSuppressionGuildOverrideSchema),
enabled_backends: z.array(VoiceNoiseSuppressionBackendSchema),
allow_user_override: z.boolean(),
stereo_enabled: z.boolean(),
suppression_strength: z.number().int().min(0).max(100),
});
export type VoiceNoiseSuppressionAssignmentResponse = z.infer<typeof VoiceNoiseSuppressionAssignmentResponse>;
export const INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT: VoiceNoiseSuppressionAssignmentResponse = {
enabled: false,
config_version: 0,
user_targeted: false,
backend: null,
source: null,
guild_overrides: [],
enabled_backends: [],
allow_user_override: false,
stereo_enabled: false,
suppression_strength: DEFAULT_VOICE_NOISE_SUPPRESSION_CONFIG.suppression_strength,
};
export function resolveVoiceNoiseSuppressionAssignment(
config: VoiceNoiseSuppressionConfig,
userId: string,
): VoiceNoiseSuppressionAssignmentResponse {
if (!config.enabled) {
return {
...INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT,
config_version: config.config_version,
};
}
const shared = {
enabled: true,
config_version: config.config_version,
enabled_backends: [...config.enabled_backends],
allow_user_override: config.allow_user_override,
stereo_enabled: config.stereo_enabled,
suppression_strength: config.suppression_strength,
};
if (config.excluded_user_ids.includes(userId)) {
return {
...shared,
enabled_backends: [],
allow_user_override: false,
stereo_enabled: false,
user_targeted: false,
backend: null,
source: null,
guild_overrides: [],
};
}
const backendIsUsable = config.enabled_backends.includes(config.default_backend);
const guildOverrides = config.guild_overrides.filter((override) =>
config.enabled_backends.includes(override.backend),
);
if (config.included_user_ids.includes(userId)) {
return {
...shared,
user_targeted: backendIsUsable,
backend: backendIsUsable ? config.default_backend : null,
source: backendIsUsable ? 'user_rule' : null,
guild_overrides: guildOverrides,
};
}
const inCanary = experimentBucket(userId, config.rollout_salt) < config.rollout_basis_points;
return {
...shared,
user_targeted: inCanary && backendIsUsable,
backend: inCanary && backendIsUsable ? config.default_backend : null,
source: inCanary && backendIsUsable ? 'canary' : null,
guild_overrides: guildOverrides,
};
}
export const VOICE_NOISE_SUPPRESSION_RESOLUTION_SOURCES = [
'user_rule',
'guild_rule',
'canary',
'user_override',
] as const;
export type VoiceNoiseSuppressionResolutionSource = (typeof VOICE_NOISE_SUPPRESSION_RESOLUTION_SOURCES)[number];
export interface VoiceNoiseSuppressionResolution {
backend: VoiceNoiseSuppressionBackend;
source: VoiceNoiseSuppressionResolutionSource;
stereoEnabled: boolean;
suppressionStrength: number;
configVersion: number;
}
export function resolveVoiceNoiseSuppressionForCall(
assignment: VoiceNoiseSuppressionAssignmentResponse,
guildId: string | null,
userPreference: VoiceNoiseSuppressionBackend | null,
): VoiceNoiseSuppressionResolution | null {
if (!assignment.enabled) return null;
const guildOverride =
guildId == null ? undefined : assignment.guild_overrides.find((override) => override.guild_id === guildId);
const targeted =
assignment.source === 'user_rule' && assignment.backend != null
? {backend: assignment.backend, source: 'user_rule' as const}
: guildOverride != null
? {backend: guildOverride.backend, source: 'guild_rule' as const}
: assignment.user_targeted && assignment.backend != null && assignment.source != null
? {backend: assignment.backend, source: assignment.source}
: null;
if (targeted == null) return null;
const shared = {
stereoEnabled: assignment.stereo_enabled,
suppressionStrength: assignment.suppression_strength,
configVersion: assignment.config_version,
};
if (
assignment.allow_user_override &&
userPreference != null &&
assignment.enabled_backends.includes(userPreference)
) {
return {...shared, backend: userPreference, source: 'user_override'};
}
return {...shared, backend: targeted.backend, source: targeted.source};
}
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
const FNV_OFFSET_BASIS_32 = 0x811c9dc5;
const FNV_PRIME_32 = 0x01000193;
export const EXPERIMENT_BUCKET_RESOLUTION = 10000;
export function experimentBucket(userId: string, salt: string): number {
let hash = FNV_OFFSET_BASIS_32;
const input = `${salt}:${userId}`;
for (let index = 0; index < input.length; index++) {
hash ^= input.charCodeAt(index) & 0xff;
hash = Math.imul(hash, FNV_PRIME_32) >>> 0;
}
return hash % EXPERIMENT_BUCKET_RESOLUTION;
}
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {z} from 'zod';
import {
INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT,
VoiceNoiseSuppressionAssignmentResponse,
} from '../admin/VoiceNoiseSuppressionSchemas';
export const EXPERIMENT_MIN_POLL_INTERVAL_SECONDS = 60;
export const EXPERIMENT_MAX_POLL_INTERVAL_SECONDS = 86400;
export const EXPERIMENT_MAX_POLL_JITTER_PERCENT = 50;
export const DEFAULT_EXPERIMENT_POLL_INTERVAL_SECONDS = 300;
export const DEFAULT_EXPERIMENT_POLL_JITTER_PERCENT = 15;
export const ExperimentDeliveryConfigSchema = z.object({
poll_interval_seconds: z
.number()
.int()
.min(EXPERIMENT_MIN_POLL_INTERVAL_SECONDS)
.max(EXPERIMENT_MAX_POLL_INTERVAL_SECONDS)
.default(DEFAULT_EXPERIMENT_POLL_INTERVAL_SECONDS),
poll_jitter_percent: z
.number()
.int()
.min(0)
.max(EXPERIMENT_MAX_POLL_JITTER_PERCENT)
.default(DEFAULT_EXPERIMENT_POLL_JITTER_PERCENT),
});
export type ExperimentDeliveryConfig = z.infer<typeof ExperimentDeliveryConfigSchema>;
export const DEFAULT_EXPERIMENT_DELIVERY_CONFIG: ExperimentDeliveryConfig = {
poll_interval_seconds: DEFAULT_EXPERIMENT_POLL_INTERVAL_SECONDS,
poll_jitter_percent: DEFAULT_EXPERIMENT_POLL_JITTER_PERCENT,
};
export const ExperimentDeliveryConfigUpdateRequest = z.object({
poll_interval_seconds: z
.number()
.int()
.min(EXPERIMENT_MIN_POLL_INTERVAL_SECONDS)
.max(EXPERIMENT_MAX_POLL_INTERVAL_SECONDS)
.optional(),
poll_jitter_percent: z.number().int().min(0).max(EXPERIMENT_MAX_POLL_JITTER_PERCENT).optional(),
});
export type ExperimentDeliveryConfigUpdateRequest = z.infer<typeof ExperimentDeliveryConfigUpdateRequest>;
export const ExperimentDeliveryConfigResponse = ExperimentDeliveryConfigSchema;
export type ExperimentDeliveryConfigResponse = z.infer<typeof ExperimentDeliveryConfigResponse>;
const ExperimentAssignmentsSchema = z.object({
voice_noise_suppression: VoiceNoiseSuppressionAssignmentResponse.optional(),
});
export const ExperimentAssignmentsResponse = z.object({
poll_interval_seconds: z.number().int(),
poll_jitter_percent: z.number().int().min(0).max(EXPERIMENT_MAX_POLL_JITTER_PERCENT),
assignments: ExperimentAssignmentsSchema,
});
export type ExperimentAssignmentsResponse = z.infer<typeof ExperimentAssignmentsResponse>;
export const INERT_EXPERIMENT_ASSIGNMENTS_RESPONSE: ExperimentAssignmentsResponse = {
poll_interval_seconds: DEFAULT_EXPERIMENT_POLL_INTERVAL_SECONDS,
poll_jitter_percent: DEFAULT_EXPERIMENT_POLL_JITTER_PERCENT,
assignments: {},
};
export function readVoiceNoiseSuppressionAssignment(
response: ExperimentAssignmentsResponse,
): VoiceNoiseSuppressionAssignmentResponse {
return response.assignments.voice_noise_suppression ?? INERT_VOICE_NOISE_SUPPRESSION_ASSIGNMENT;
}