This commit is contained in:
2026-04-18 23:11:35 +02:00
commit f7cfd2a86e
196 changed files with 35538 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
// Audio quality preferences for outgoing voice/music. `voice` is the default
// — Opus 48 kbps stereo with full DSP (echo cancellation + noise suppression
// + AGC). `hifi` bumps to 96 kbps stereo Opus and disables all DSP so music,
// instruments, or broadcast-style voice streams stay uncoloured.
const STORAGE_KEY = 'chatapp.audio';
export type AudioQuality = 'voice' | 'hifi';
export interface AudioSettings {
quality: AudioQuality;
}
const DEFAULTS: AudioSettings = {
quality: 'voice',
};
export interface AudioQualityParams {
label: string;
bitrateKbps: number;
stereo: boolean;
sampleRateHz: number;
// DSP toggles — off for hifi so music isn't coloured by noise-suppression.
echoCancellation: boolean;
noiseSuppression: boolean;
autoGainControl: boolean;
}
const PARAMS: Record<AudioQuality, AudioQualityParams> = {
voice: {
label: 'Voice · 48 kbps',
bitrateKbps: 48,
stereo: false,
sampleRateHz: 48_000,
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
hifi: {
label: 'HiFi · 510 kbps Stereo',
bitrateKbps: 510, // Opus max, ~CD-quality stereo
stereo: true,
sampleRateHz: 48_000,
echoCancellation: false,
noiseSuppression: false,
autoGainControl: false,
},
};
export const AUDIO_QUALITY_ORDER: ReadonlyArray<AudioQuality> = ['voice', 'hifi'];
export function getAudioQualityParams(q: AudioQuality): AudioQualityParams {
return PARAMS[q];
}
type Listener = (s: AudioSettings) => void;
const listeners = new Set<Listener>();
let cached: AudioSettings | null = null;
function isQuality(v: unknown): v is AudioQuality {
return v === 'voice' || v === 'hifi';
}
function read(): AudioSettings {
if (cached) return cached;
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) {
cached = DEFAULTS;
return cached;
}
const parsed = JSON.parse(raw) as Partial<AudioSettings>;
cached = {
quality: isQuality(parsed.quality) ? parsed.quality : DEFAULTS.quality,
};
return cached;
} catch {
cached = DEFAULTS;
return cached;
}
}
function write(s: AudioSettings): void {
cached = s;
try {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(s));
} catch {
/* quota / private mode */
}
for (const l of listeners) l(s);
}
export function getAudioSettings(): AudioSettings {
return read();
}
export function updateAudioSettings(patch: Partial<AudioSettings>): AudioSettings {
const next = { ...read(), ...patch };
write(next);
return next;
}
export function subscribeAudioSettings(listener: Listener): () => void {
listeners.add(listener);
return () => listeners.delete(listener);
}