initial
This commit is contained in:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user